day01
1. 转义字符的判断
以下不正确的定义语句是( )
A: double x[5] = {2.0, 4.0, 6.0, 8.0, 10.0};
B: char c2[] = {'\x10', '\xa', '\8'};
C: char c1[] = {'1','2','3','4','5'};
D: int y[5+3]={0, 1, 3, 5, 7, 9};
【答案解析】 B
本题B选项考查转义字符,==有如下格式,但八进制数字是0-7,没有8,故B选项中'\8'是错误的==
\ddd ddd表示1到3个八进制数 如:\130 转义为 字符X
\xhh hh表示1到2位十六进制数 如:\x30 转义为 字符0
2. &数组名和数组名
有如下定义语句,则正确的输入语句是【多选】( )
int b; char c[10];
A: scanf("%d%s",&b,&c);
B:scanf("%d%s",&b,c);
C: scanf("%d%s",b,c);
D: scanf("%d%s",b,&c);
【答案解析】AB
==&c和c两个地址值是一样的,程序的效果相同,== 也没错,但同时也==必须把变量b的地址给scanf,== 故CD错误,AB正确
3. 动态内存开辟的使用场景
这里设计的函数返回值为int 返回一个整数列表,如果只是单纯的在函数体内部创建整数列表作为返回值,那么就*大错特错,在函数体内创建的列表只在该函数体内有效,退出函数体,函数栈帧就销毁了,该指针属于野指针
day02
4. 计算字符个数(转义字符)
以下程序段的输出结果是( )
#include<stdio.h> int main() { char s[] = "\\123456\123456\t"; printf("%d\n", strlen(s)); return 0; }
A: 12 B: 13 C: 16 D: 以上都不对
【答案解析】A
这里考查转义字符,注意:==\ 表示字符'\',\123表示字符'{',\t表示制表符,==这些都是一个字符
5. 浮点型的比较 和 数组的初始化
对于下面的说法,正确的是( )
A: 对于 struct X{short s;int i;char c;},sizeof(X)等于sizeof( s ) + sizeof( i ) + sizeof( c )
B: 对于某个double变量 a,可以使用 a == 0.0 来判断其是否为零
C: 初始化方式 char a[14] = "Hello, world!"; 和char a[14]; a = "Hello, world!";的效果相同
D: 以上说法都不对
【答案解析】D
A选项,没有考虑内存对齐。
B选项,考察double类型的比较,由于浮点数存在误差,不能直接判断两个数是否相等,通常采用比较两数之差的绝对值是否小于一个很小的数字(具体的可自己设定这样一个数,作为误差)来确定是否相等。
==C选项,a为数组首地址是常量不能改变,==
所以A,B,C都是错的,选择D
6. while(~scanf(..))的用法
==while (~scanf("%d%d",&n,&m))
等效于 while (scanf("%d%d",&n,&m)!=EOF)
==
因为读到文件的结束符时,scanf返回值是EOF,也就是-1,而~(-1)的作用就是对-1的按位取反。
-1的原码是1000 0001,因此-1的补码就是1111 1111。
~(-1)就是包括符号位都取反,变成0000 0000
day03
7. 全局变量的使用
以下程序的输出结果为( )
#include <stdio.h> int i; void prt() { for (i = 5; i < 8; i++) printf("%c", '*'); printf("\t"); } int main() { for (i = 5; i <= 8; i++) prt(); return 0; }
A: *** B: *** *** *** *** C: *** *** D: * * *
答案解析:
正确答案:A
全局变量i,在main()中修改为5,==第一次在prt()中执行循环输出三次'',i被修改为8,== 回到main()中第二次调用prt()
时,i<8为假,循环结束没输出,执行一次print("\t"),*再次回到主函数后i++变为9, i<=8为假,循环结束;
8. 二分查找 和 追加思想
【答案解析】
暴力破解:遍历数组找出最小值即可
==更优思想:采用二分查找,这个题主要分析三种旋转情况 [1, 2, 3, 4, 5],使用中间值与右端进行比较。==
- 中间大于右边 [3, 4, 5, 1, 2],这种情况下,最小数一定在右边;则left = middle + 1
- 中间等于右边 [1, 0, 1, 1, 1], 这个是[0, 1, 1, 1, 1] 旋转过来的,这时候需要缩小范围 right--;,注意不能是
left++,因为是非降序数组,所以要缩小右边范围,把较小值向右推,符合我们的判断规则。 - 中间小于右边 [5, 1, 2, 3, 4], 这种情况下,最小数字则在左半边;则right = middle
==最优思想:在数组后追加自身数组,找到第一个比之前数组元素小的数字==int minNumberInRotateArray(int* rotateArray, int rotateArrayLen) { if (rotateArrayLen == 0) return 0; int left = 0, right = rotateArrayLen - 1, mid; if (rotateArray[right] > rotateArray[left]) return rotateArray[0]; while (left < right) { mid = left + (right - left) / 2; if (rotateArray[mid] > rotateArray[right]) left = mid + 1; else if (rotateArray[mid] == rotateArray[right]) right--; else right = mid; } return rotateArray[left]; }
day04
9. for循环第一项
设变量已正确定义,以下不能统计出一行中输入字符个数(不包含回车符)的程序段是( )
A:n=0;while(ch=getchar()!='\n')n++;
B:n=0;while(getchar()!='\n')n++;
C:for(n=0;getchar()!='\n';n++);
D:n=0;for(ch=getchar();ch!='\n';n++);
【答案解析】D
对于for循环,其中第一项初始化表达式: ch=getchar();只执行一次,因此ch只从输入流中取一个字符,
之后就再不会取字符,因此会死循环
10. 回车 == '\n'
若运行以下程序时,从键盘输入 ADescriptor<回车> ,则下面程序的运行结果是( )
#include <stdio.h> int main() { char c; int v0 = 0,v1 = 0,v2 = 0; do { switch (c = getchar()) { case'a':case'A': case'e':case'E': case'i':case'I': case'o':case'O': case'u':case'U':v1 += 1; default:v0 += 1; v2 += 1; } } while (c != '\n'); printf("v0=%d,v1=%d,v2=%d\n", v0, v1, v2); return 0; }
A: v0=7,v1=4,v2=7
B: v0=8,v1=4,V2=8
C: v0=11,v1=4,v2=11
D: v0=12,v1=4,v2=12
答案解析:
正确答案:D
代码switch语句中没有break,则每次找到入口进入后,顺序执行到代码块结束为止。例如当c为'A'时,从case 'A'进入,
先后执行v1+=1;v0+=1;v2+=1;,而当c为'p'时,从default进入,先后执行v0+=1;v2+=1;,容易看出最终v0和v2是相
等的
而回车表示的 ' \n ' 10 ,会进入default 然后再跳出循环,所以一共是12次
11. 插入排序和选择排序的区别
以下程序段的功能是( )
int a[] = { 4, 0, 2, 3, 1 }, i, j, t; for (i = 1; i < 5; i++) { t = a[i]; j = i - 1; while (j >= 0 && t < a[j]) { a[j + 1] = a[j]; --j; } a[j + 1] = t; }
A: 对数组a进行插入排序(升序) B: 对数组a进行插入排序(升序)
C: 对数组a进行选择排序(升序) D: 对数组a进行选择排序(降序)
【答案解析】B
这是一个升序插入排序算法,读完即懂。==结合画图分析==
第i次排序时,t=a[i]作为临时变量保存这一次待插入值,j=i-1故而while循环中j是从下标i的前一项开始向下标0遍历,
判断t<a[j]为真时a[j+1]=a[j],j+1在遍历之初是等于i的,也就是将下标i位置用前边较大的值覆盖,依次把前边的元素后移,直到a[j]不大于t的时候将t插入到下标j+1位置,使得前i个元素达到有序,
方便第i+1次排序操作,所以第i次排序时前边i-1个元素都是有序的
12. 标记思想(最优) 和 排序思想(暴力)
1、【答案解析】:
使用标记的方式就可以找出重复的数字,数组中出现过哪个数字就把对应数字作为下标在对应位置1,表示已经标记出现过,如果哪个数据对应位已经置1,则表示就是重复的数字。有了重复的数字,拿 [1, n] 的总和减去去掉重复数据的数组总和就是丢失的数据。
==标准版:== 其实使用标记法时出现的数字对应位每次 ++ ,则最后出现0次的就是丢失,出现2次的就是重复的,这样的方式也可以,不过需要多一次遍历。
int* findErrorNums(int* nums, int numsSize, int* returnSize) {
*returnSize = 2;
//遍历nums数组,将其中数据对应的位置1, 哪一位如果已经重置过则意味着数据重复了
int* arr = (int*)calloc(numsSize + 1, sizeof(int));//申请numsSize个整形空间,并初始化为0
int* ret = (int*)calloc(*returnSize, sizeof(int));//申请2个整形空间,并初始化为0
int cur_sum = 0, old_sum = 0;
for (int i = 0; i < numsSize; i++) {
if (arr[nums[i]] == 1) {
//这个数字在上边数组的对应位置已经置过1了,则重复
ret[0] = nums[i];//找到重复的数字
}
arr[nums[i]] = 1; //将标记数组的对应数据位置1
old_sum += i + 1; // 1~n的求和
cur_sum += nums[i]; //当前数组中的数据求和(多了一个重复的,少了一个丢失的)
}
ret[1] = old_sum - (cur_sum - ret[0]);//原始总和,减去去掉重复后的当前总和就是丢失的数字
free(arr);
return ret;
}
==标准版:(自己实现)==
int* findErrorNums(int* nums, int numsSize, int* returnSize) {
*returnSize = 2;
int* same = (int*)malloc(sizeof(int) * numsSize);
assert(same);
memset(same, 0, numsSize*4);
for (int i = 0; i < numsSize; i++)
{
same[nums[i]-1]++;
}
int* tmp = (int*)malloc(8);
for (int i = 0; i < numsSize; i++)
{
if (same[i] == 0)
{
tmp[1] = i + 1;
}
if (same[i] == 2)
{
tmp[0] = i + 1;
}
}
return tmp;
}
暴力方法:将数组排好序,找到出现问题的地方,针对特殊情况还需要处理,非常麻烦 o(╥﹏╥)o
day05
12. 二分思想的运用场景
当题目中出现非降序和时间复杂度O(logN)的关键信息时,可采取二分的思想
13. unsigned 类型
==如果使用int 类型,那么A^B得到的是若是负数-1,会发生溢出的现象(-1的补码为全1)死循环,所以采取unsigned int 类型==
day06
14. do while循环
以下描述中正确的是( )
A: 由于do-while循环中循环体语句只能是一条可执行语句,所以循环体内不能使用复合语句
B: do-while循环由do开始,用while结束,在while(表达式)后面不能写分号
C: 在do-while循环体中,不一定要有能使while后面表达式的值变为零("假")的操作
D: do-while循环中,根据情况可以省略while
【答案解析】C
do-while循环中的循环体通常都是复合语句代码块,A错误,
while(表达式)后面要写分号,B错误,
while不能省,D错误
C中如果出现没有使while后面表达式的值变为零("假")的操作,则表示死循环(do...while...是可以出现死循环的情况的类似于while(1))
15. 默认返回类型
在c语言中,一个函数不写返回值类型,默认的返回类型是( )
A: int B: char C: void D: 都不是
【答案解析】A
==一个函数不写返回值类型,默认的返回类型是int(可不是void喔),== 但不提倡这么做,代码可读性不高
day08
16. 数组的定义
下列定义数组的语句中正确的是【多选】( )
A:#define size 10 char str1[size], str2[size+2];
B:
char str[];
C:int num['10'];
D:int n=5; int a[n][n+2];
答案解析:
正确答案:AC
A选项:宏替换,没问题;
B选项:非法定义,一维数组必须定义数组元素个数;
C选项:字符'0',转换成十进制为48,所以该选项最终为int num[48];
D选项:错误,数组定义下角标不能为变量,注:C99标准中支持了使用变量,这里不做特殊考虑。
17. 抵消思想
【答案解析】:
一个数组中有一个数字出现次数大于 n/2 ,从第 0 个字符开始,假设它就是最多的那个数字,遇到相同的数字则计数 +1 , 遇到不同的则计数 -1 ,其实就是互相消耗,等到计数为 0 的时候,表示本次互拼完毕,从下一个字符重新开始互拼,但是归根结底出现次数大于 n/2 的这个数字数量更多,因此也是最后保留的字符。
示例: "23335" 首先从字符 2 开始计数 1 ,遇到 3 ,不同则 -1 ,互拼消耗 重新从剩下的 "335" 开始的过程,这时候保存的字符为 3 ,遇到 3 则计数 +1 , 遇到5则计数 -1 ,在计数不为 0 时,走到末尾保存的字符就是个数超过 n/2的字符
int majorityElement(int* nums, int numsSize) {
int count = 1;
int tmp = nums[0];
for (int i = 1; i < numsSize; i++) {
if (tmp == nums[i]) {
//与保存的字符相同则计数+1
count++;
}
else {
//与保存的字符不同则计数-1
count--;
//计数为0表示有可能保存的字符不是最多的字符,换下一个
if (count == 0) tmp = nums[i + 1];
}
}
return tmp;
}
day09
18. 指针数组
下列程序的输出是( )
#include<stdio.h> int main() { int a[12] = { 1,2,3,4,5,6,7,8,9,10,11,12 }, *p[4], i; for (i = 0; i < 4; i++) p[i] = &a[i * 3];// printf("%d\n",p[3][2]); return 0; }
A: 上述程序有错误 B: 6 C: 8 D: 12
【答案解析】D
==p是一个指针数组,(int* p[4]
)==
p[i] = &a[i3]相当于是把数组a每3个一组 *分开并把每组的首地址存在p数组,此时p类似一个4行3列的二维数组,p[3][2]
就是4行第3个元素12
19. 逗号表达式
以下逗号表达式的值为( )
(x = 4 * 5 , x * 5) , x + 5;
A: 25 B: 20 C: 100 D: 45
【答案解析】A
==逗号表达式的优先级是最低的,所以最后计算== 此题去掉括号后的表达式,和原表达式是等价的,先计算4*5
并赋值给x,x变为20,中间x*5
并没有改变x的值,最后一项x+5
值是25,也就是整个表达式的值
20. 左右划分
【答案解析】:
暴力不考虑其他的因素的话,将所有数据乘积起来,然后遍历数组除以当前位置数据即可。
更优解法:将乘积分为两次进行,第一次先将每个位置左边的数据乘积计算出来放到返回数组中,后边第二次循环将
对应位置右边的数据乘积计算出来与返回数组对应位置的左半边乘积相乘得到结果。
示例: 一个数组 int nums[] = {2, 3, 4} 。 int left = 1, right = 1; 计算左侧乘积:
第0个元素的左边乘积, arr[0] = left 然后计算第1位左侧乘积left*=nums[0] -> left = 1*2
第1个元素的左边乘积, arr[1] = left 然后计算第2位左侧乘积left*=nums[1] -> left = 1*2*3
第2个元素的左边乘积, arr[2] = left 然后计算第3位左侧乘积 已经没必要了,因为第2元素是末尾元素了
一次循环完毕后,返回数组中每个元素存储的都是自己左侧元素的乘积。 arr[]中的值: [1, 2, 6] 计算右侧乘积:
第2个元素的右边乘积,arr[2] *= right
然后计算第1位右侧乘积right*=nums[2] -> right =1*4
第1个元素的右边乘积,arr[1] *= right
然后计算第0位右侧乘积right*=nums[1] -> right =1*4*3
第0个元素的右边乘积,arr[0] *= right
然后计算第-1位右侧乘积 -1位已经不需要计算了
循环完毕后,返回数组中的每个元素都是其他元素的乘积了arr[2]*=1; arr[1]*=4; arr[0]*=12
==空间复杂度:O(1)==
int* productExceptSelf(int* nums, int numsSize, int* returnSize) {
int* ret = (int*)malloc(numsSize * sizeof(int));
*returnSize = numsSize;
int left = 1, right = 1;
//第一次循环,将当前位置左边的数字乘积填入返回数组中
for (int i = 0; i < numsSize; i++) {
ret[i] = left;// 1 nums[0] nums[0]*nums[1] num[0]*nums[1]*nums[2] ....
left *= nums[i];
}
//第二次循环,对于返回数组的元素从后往前进行,每次乘以右边元素的乘积
for (int i = numsSize - 1; i >= 0; i--) {
ret[i] *= right; //最后一个成绩不需要乘以最后元素,乘以1就行
right *= nums[i]; //right变化:1 nums[end] nums[end]*nums[end-1] .....
}
return ret;
}
==空间复杂度:O(N) 更容易理解==
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* productExceptSelf(int* nums, int numsSize, int* returnSize)
{
//除自身的乘积的的值 等于 左乘积 * 右乘积
int leftPro[numsSize];
int rightPro[numsSize];
//左乘积
leftPro[0] = 1;
for(int i = 1; i < numsSize; i++)
{
leftPro[i] = leftPro[i-1] * nums[i-1];
}
//右乘积
rightPro[numsSize-1] = 1;
for(int i = numsSize - 1 - 1; i >= 0 ; i--)
{
rightPro[i] = rightPro[i+1] * nums[i+1];
}
*returnSize = numsSize;
// 返回数组
int * returnNums = (int *)malloc(sizeof(int) * numsSize);
for(int i =0; i < numsSize; i++)
{
returnNums[i] = leftPro[i] * rightPro[i];
}
return returnNums;
}