🔥个人主页:艾莉丝努力练剑
❄专栏传送门:《C语言》、《数据结构与算法》、C语言刷题12天IO强训、LeetCode代码强化刷题、洛谷刷题、C/C++基础知识知识强化补充、C/C++干货分享&学习过程记录
🍉学习方向:C/C++方向学习者
⭐️人生格言:为天地立心,为生民立命,为往圣继绝学,为万世开太平
前言:距离我们学完C语言已经过去一段时间了,在学习了初阶的数据结构之后,博主还要更新的内容就是【C语言16天强化训练】,之前博主更新过一个【C语言刷题12天IO强训】的专栏,那个只是从入门到进阶的IO模式真题的训练。【C语言16天强化训练】既有IO型,也有接口型。和前面一样,今天依然是训练五道选择题和两道编程算法题,希望大家能够有所收获!
目录
正文
一、五道选择题
1.1 题目1
1.2 题目2
1.3 题目3
1.4 题目4
1.5 题目5
二、两道算法题
2.1 图片整理
2.1.1 题目理解
2.1.2 思路
2.2 寻找数组的中心下标
2.2.1 尝试暴力写代码
2.2.2 加深题目理解
2.2.3 优化思路
2.2.4 优化方案实现
结尾
正文
一、五道选择题
1.1 题目1
题干:以下对C语言函数的有关描述中,正确的有( )【多选】
A. 在C语言中,一个函数一般由两个部分组成,它们是函数首部和函数体
B. 函数的实参和形参可以是相同的名字
C. 在main()中定义的变量都可以在其它被调函数中直接使用
D. 在C程序中,函数调用不能出现在表达式语句中
解析:正确选项是AB。
A. 正确。函数首部包括函数返回类型、函数名和参数列表,函数体包括一对花括号内的语句序列。
B. 正确。实参和形参的作用域不同,实参是调用函数时的实际值,形参是函数定义中的变量,它们可以同名,但代表不同的实体。
C. 错误。在main函数中定义的变量是局部变量,只能在main函数内部使用,其他函数不能直接访问。除非这些变量是全局变量或通过参数传递。
D. 错误。函数调用可以出现在表达式语句中,例如:int result = add(a,b); 或 printf("Hello"); 都是表达式语句中包含函数调用。
1.2 题目2
题干:在C语言中,以下正确的说法是( )
A. 实参和与其对应的形参各占用独立的存储单元
B. 实参和与其对应的形参共占用一个存储单元
C. 只有当实参和与其对应的形参同名时才共占用存储单元
D. 形参是虚拟的,不占用存储单元
解析:正确选项是A选项。
A. 正确。实参和形参各占用独立的存储单元。
B. 错误。它们不共享存储单元,而是各自独立。
C. 错误。是否同名不影响存储单元的占用,即使同名也是不同的存储单元(因为作用域不同);
D. 错误。形参是函数内的局部变量,会占用存储单元(通常在栈上)。
在C语言中,函数参数的传递是值传递(pass by value)。这意味着:
(1)当函数被调用时,实参的值会被复制到形参中;
(2)因此,实参和形参占用不同的存储单元(即独立的内存空间);
(3)对形参的修改不会影响实参(除非通过指针间接修改)。
1.3 题目3
题干:在上下文及头文件均正常的情况下,下列代码的输出是( )(注:print 已经声明过了)
int main()
{char str[] = "Geneius";print(str);return 0;
}
print(char *s)
{if(*s){print(++s);printf("%c", *s);}
}
A. suiene B. neius C. run-time error D. suieneG
解析:正确选项是A选项。
代码理解:
1、main函数定义了一个字符串 str = "Geneius",然后调用 print(str); 。
2、printf 函数是一个递归函数——
(1)如果当前字符
*s
不是空字符(即字符串未结束),则递归调用print(++s)
(注意:这里先递增s
,然后传入递归)。(2)递归返回后,打印当前字符
*s
(注意:此时s
已经递增过一次,所以指向的是下一个字符)。
执行过程:
原始字符串: "Geneius"(注意:字符串以空字符 '\0' 结尾)。
递归调用过程(每次调用 print(++s),所以指针不断后移):
第一次调用:s指向"Geneius" -> 递归调用 print(++s)(现在s指向"eneius");
第二次调用:s指向"eneius" -> 递归调用 print(++s)(现在s指向"neius");
第三次调用:s指向"neius" -> 递归调用 print(++s)(现在s指向"eius");
第四次调用:s指向"eius" -> 递归调用 print(++s)(现在s指向"ius");
第五次调用:s指向"ius" -> 递归调用 print(++s)(现在s指向"us");
第六次调用:s指向"us" -> 递归调用 print(++s)(现在s指向"s");
第七次调用:s指向"s" -> 递归调用 print(++s)(现在s指向空字符'\0');
第八次调用:s指向'\0',条件if(*s)为假,递归终止并返回。
调用完然后开始回溯(从最深层的递归返回):
第七次调用:s原本指向"s",但调用时是print(++s),所以递归返回后,s现在指向空字符(即'\0'之后?实际上已经越界了)。但注意:在递归调用中,s的值已经被改变(因为++s是前置递增,修改了s本身)。所以每次递归返回后,s指向的是原来字符串的下一个字符(甚至最后是空字符之后)。
具体回溯时打印的字符:
从最深层的递归(第八次调用)返回第七次调用:此时s指向空字符(即'\0')之后?但实际第七次调用时,传入的s是递增后的(指向空字符),然后递归返回后,s仍然指向空字符(但字符串已经结束)。所以打印的是空字符(不显示),不过这里的代码实际上是有问题的。
关键错误:
在递归调用中,使用了 print(++s),这修改了指针s的值。当递归返回时,s已经指向下一个字符(而不是原来的字符)。因此——
(1)第一次递归调用后,s指向"eneius"(即第二个字符);
(2)当递归返回时,打印的是当前s指向的字符(即第二个字符'e'),而不是第一个字符'G'。
类似地,整个回溯过程打印的是字符串从第二个字符开始逆序 (但最后一个是空字符之后,未定义) ,实际上,字符串"Geneius"的长度为7 (加上空字符共8个字节)。递归调用直到遇到空字符(第八次调用时,s指向空字符,递归停止) 。然后回溯——
第七次调用:s原本指向"s"(最后一个字符),但调用print(++s)后,s指向空字符。然后递归返回后,打印*s(即空字符,不显示)。
第六次调用:s原本指向"us"(即字符'u'),调用print(++s)后,s指向"s"。递归返回后,打印*s(即's')。
第五次调用:s原本指向"ius"(即字符'l'),调用print(++s)后,s指向"us"。递归返回后,打印*s(即'u')。
第四次调用:s原本指向"eius"(即字符'e'),调用print(++s)后,s指向"lus"。递归返回后,打印*s(即'l')。
第三次调用:s原本指向"neius"(即字符'n'),调用print(++s)后,s指向"eius"。递归返回后,打印*s(即'e')。
第二次调用:s原本指向"eneius"(即字符'e'),调用print(++s)后,s指向"neius"。递归返回后,打印*s(即'n')。
第一次调用:s原本指向"Geneius"(即字符'G'),调用print(++s)后,s指向"eneius"。递归返回后,打印*s(即'e')。
所以打印的字符序列是(从最深回溯):' '(空字符,不显示)、's'、'u'、'i'、'e'、'n'、'e'。
即最终输出为:"suien"(但缺少第一个字符'G',并且似乎多打了一个'e')。
实际上,输出应该是:"suiene"(但注意第一个字符'e'是第二次调用打印的,而第一次调用打印的是'e'(第二个字符))。
观察字符串"Genelus":
索引0: 'G'
索引1: 'e'
索引2: 'n'
索引3: 'e'
索引4: 'i'
索引5: 'u'
索引6: 's'
索引7: '\0'
回溯打印:
递归深度7(s指向索引7): 打印*s(即'\0',不显示);
递归深度6(s指向索引6): 打印*s(即's');
递归深度5(s指向索引5): 打印*s(即'u');
递归深度4(s指向索引4): 打印*s(即'i');
递归深度3(s指向索引3): 打印*s(即'e');
递归深度2(s指向索引2): 打印*s(即'n');
递归深度1(s指向索引1): 打印*s(即'e')。
所以输出是:suiene(即"suiene")。
注意:原字符串是"Geneius",逆序应为"suieneG",但这里由于递归偏移,打印的是从第二个字符开始逆序(即"eneius"的逆序)为"suiene",而没有第一个'G',所以输出是"suiene"。
选项D是"suieneG"(多了一个G),实际上,代码不会打印第一个字符'G',因为第一次调用就递增了指针;另外,最后递归到空字符时,尝试打印空字符(不显示),所以没有额外输出。
我们来看这四个选项——
选项A正确;
选项B "neius" 是正序"Geneius"的一部分(错误);
选项C "run-time error":可能因为最后打印空字符(但不会错误,只是不显示),或者指针越界(但通常不会立即错误);
选项D "suieneG" 包含第一个字符'G',但实际没有。
所以正确答案就是A. suiene。
1.4 题目4
题干:对于函数 void f(int x); ,下面调用正确的是( )
A. int y=f(9); B. f(9); C. f(f(9)); D. x=f();
解析:正确选项是B选项。
于函数 void f(int x); ,这是一个返回类型为 void 的函数,即它不返回任何值。因此:
A. 错误,因为 f(9) 没有返回值,不能赋值给 int y。
B. 正确,直接调用函数,不需要使用返回值。
C. 错误,因为 f(9) 没有返回值,不能作为 f 的参数。
D. 错误,因为 f 需要一个 int 类型的参数,这里没有提供参数;而且 f 没有返回值,不能赋值给 x。
1.5 题目5
题干:给定 fun 函数如下,那么 fun(10) 的输出结果是( )
int fun(int x)
{return (x==1) ? 1 : (x + fun(x-1));
}
A. 0 B. 10 C. 55 D. 3628800
解析:正确选项是C选项。
这道题很简单,函数 fun(int x) 是一个递归函数,其逻辑如下:
1)如果 x == 1,返回 1;
2)否则,返回 x + fun(x-1)。
因此,fun(10) 的计算过程如下所示:
fun(10) = 10 + fun(9)
fun(9) = 9 + fun(8)
fun(8) = 8 + fun(7)
fun(7) = 7 + fun(6)
fun(6) = 6 + fun(5)
fun(5) = 5 + fun(4)
fun(4) = 4 + fun(3)
fun(3) = 3 + fun(2)
fun(2) = 2 + fun(1)
fun(1) = 1
我们代入计算之后得到——
fun(2) = 2 + 1 = 3
fun(3) = 3 + 3 = 6
fun(4) = 4 + 6 = 10
fun(5) = 5 + 10 = 15
fun(6) = 6 + 15 = 21
fun(7) = 7 + 21 = 28
fun(8) = 8 + 28 = 36
fun(9) = 9 + 36 = 45
fun(10) = 10 + 45 = 55
所以,fun(10) 的结果是 55,正确答案选择C选项。
选择题答案如下:
1.1 AB
1.2 A
1.3 A
1.4 B
1.5 C
校对一下,大家都做对了吗?
二、两道算法题
2.1 图片整理
牛客网链接:HJ34 图片整理
题目描述:
2.1.1 题目理解
我们根据题目要求,需要对输入的字符串按照ASCII码值进行排序。
这道题是接口型的,下面是C语言的模版(如果是IO型就可以不用管它们了)——
2.1.2 思路
(1)使用scanf读取输入字符串;
(2)使用qsort函数进行排序,需要提供一个;
(3)比较函数compare比较函数直接通过字符的ASCII码值相减来确定顺序;
(4)最后使用printf输出排序后的字符串。
代码演示:
//C语言
#include <stdio.h>
#include <stdlib.h>
#include <string.h>int compare(const void* a, const void* b)
{return (*(char*)a - *(char*)b);
}int main()
{char s[1001];scanf("%s", s);int len = strlen(s);qsort(s, len, sizeof(char), compare);printf("%s", s);return 0;
}
时间复杂度:O(1);
空间复杂度:O(1)。
我们学习了C++之后也可以尝试用C++来实现一下,看看自己前段时间C++学得怎么样——
代码演示:
//C++复盘
#include <iostream>
#include"string"
#include"algorithm"
using namespace std;int main()
{string s;cin >> s;sort(s.begin(), s.end());cout << s << endl;return 0;
}
时间复杂度:O(logn),空间复杂度:O(1)。
我们目前要写出来C++的写法是非常考验前面C++的学习情况好不好的,大家可以尝试去写一写,优先掌握C语言的写法,博主还没有介绍C++的算法题,之后会涉及的,敬请期待!
2.2 寻找数组的中心下标
链接:724. 寻找数组的中心下标
力扣题解链接:前缀和优化法解决【寻找数组的中心下标】问题
题目描述:
2.2.1 尝试暴力写代码
我们先尝试用传统暴力方法:(循环)直接解题——
大致思路:
// 时间复杂度 O(n²)
for (int i = 0; i < n; i++)
{int leftSum = 0, rightSum = 0;// 计算左侧和:O(i)for (int j = 0; j < i; j++) {leftSum += nums[j];}// 计算右侧和:O(n-i-1)for (int j = i + 1; j < n; j++) {rightSum += nums[j];}if (leftSum == rightSum) return i;
}
有了第一种思路,博主展示一下用【暴力方法】解题的代码:
//C语言实现——迭代暴力方法
int pivotIndex(int* nums, int numsSize)
{for (int i = 0; i < numsSize; i++){int leftsum = 0, rightsum = 0;for (int j = 0; j < i; j++){leftsum += nums[j];}for (int j = i + 1; j < numsSize; j++){rightsum += nums[j];}if (leftsum == rightsum){return i;}}return -1;
}
时间复杂度:O(n^2);
空间复杂度:O(1)。
我们不推荐这种方法,时间复杂度不好,还能再优化一下。
2.2.2 加深题目理解
我们可以用前缀和优化法处理诸如【寻找数组的中心下标】这类"寻找平衡点"问题; 这种方法避免了每次计算左右两侧和的重复计算,通过数学关系直接判断,效率很高。
2.2.3 优化思路
(1)计算总和:首先遍历整个数组,计算所有元素的总和;
(2)遍历寻找中心下标:再次遍历数组,维护一个左侧和的变量;
(3)检查条件:对于每个位置i,检查 左侧和 == 总和 - 左侧和 - 当前元素,
等价于检查 2 * 左侧和 == 总和 - 当前元素;
(4)返回结果:如果找到满足条件的位置,立即返回;如果遍历完都没找到,返回-1。
这道题是接口型的,下面是C语言的模版(如果是IO型就可以不用管它们了)——
2.2.4 优化方案实现
代码演示:
int pivotIndex(int* nums, int numsSize) {int total = 0;// 计算数组所有元素的总和for (int i = 0; i < numsSize; i++){total += nums[i];}int leftSum = 0;for (int i = 0; i < numsSize; i++){// 右侧元素之和 = 总和 - 左侧元素之和 - 当前元素if (leftSum == total - leftSum - nums[i]){return i;}leftSum += nums[i];}return -1;
}
时间复杂度:O(1);
空间复杂度:O(1)。
我们学习了C++之后也可以尝试用C++来实现一下,看看自己前段时间C++学得怎么样——
代码演示:
//C++实现
#include<vector>
using namespace std;class Solution {
public:int pivotIndex(vector<int>& nums) {int total = 0;// 计算数组所有元素的总和for (int num : nums){total += num;}int leftSum = 0;for (int i = 0; i < nums.size(); i++){// 右侧元素之和 = 总和 - 左侧元素之和 - 当前元素if (leftSum == total - leftSum - nums[i]){return i;}leftSum += nums[i];}return -1;}
};
时间复杂度:O(n),空间复杂度:O(1)。
我们目前要写出来C++的写法是非常考验前面C++的学习情况好不好的,大家可以尝试去写一写,优先掌握C语言的写法,博主还没有介绍C++的算法题,之后会涉及的,敬请期待!
结尾
本文内容到这里就全部结束了,希望大家练习一下这几道题目,这些基础题最好完全掌握!
往期回顾:
【C语言16天强化训练】从基础入门到进阶:Day 6
【C语言16天强化训练】从基础入门到进阶:Day 5
【C语言16天强化训练】从基础入门到进阶:Day 4
【C语言16天强化训练】从基础入门到进阶:Day 3
【C语言16天强化训练】从基础入门到进阶:Day 2
【C语言16天强化训练】从基础入门到进阶:Day 1
结语:感谢大家的阅读,记得给博主“一键四连”,感谢友友们的支持和鼓励!