【C语言16天强化训练】从基础入门到进阶:Day 8
🔥个人主页:艾莉丝努力练剑
❄专栏传送门:《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.1.3 分治思想解决
2.2 字符个数统计
2.2.1 题目初步理解
2.2.2 题目深入理解
2.2.3 思路
2.2.4 简化的C语言实现
2.2.5 C++实现
2.2.6 简化C++实现
结尾
正文
一、五道选择题
1.1 题目1
题干:如下程序的运行结果是( )
char c[5]={'a', 'b', '\0', 'c', '\0'};
printf("%s", c);
A. 'a' 'b' B. ab\0c\0 C. ab c D. ab
解析:正确答案是D。printf 使用 %s 格式符输出字符串时,会从数组的起始地址开始打印字符,直到遇到第一个空字符(' \0 ')为止。空字符表示字符串的结束。
数组 c 的前三个元素是:' a ', ' b ', ' \0 ' 。因此,当 printf 读取到第三个元素(' \0 ')时,就会停止输出。因此,实际输出的字符串是 "ab"。
A. 'a' 'b' 看起来像是两个字符,但输出实际上是字符串 "ab"(没有单引号)。
B. ab\0c\0 显示了转义序列,但 printf 不会输出空字符或之后的字符。
C. ab c 中间有一个空格,但实际输出没有空格。
D. ab 是正确的输出。
1.2 题目2
题干:若有定义: int a[2][3]; ,以下选项中对 a 数组元素正确引用的是( )
A. a[2][0] B. a[2][3] C. a[0][3] D. a[1>2][1]
解析:正确答案(唯一不越界的选项)是D选项。
根据定义 int a[2][3]; ,这是一个二维数组,有2行 (行索引0和1) 和3列 (列索引0、1、2) 。
现在分析每个选项的合法性(即是否越界)——
A. a[2][0] 行索引2(最大行索引是1),越界。
B. a[2][3] 行索引2(最大1)和列索引3(最大2),都越界。
C. a[0][3] 列索引3(最大2),越界。
D. a[1>2][1] 表达式 1 > 2 为假(0),所以相当于 a[0][1],行索引0(有效)和列索引1(有效),是不越界的。
我们来仔细分析一下C选项——
C选项: a[0][3]
对于数组 int a[2][3];
:
(1)行索引范围:0~1(共2行);
(2)列索引范围:0~2(共3列)。
a[0][3]
表示:
(1)行索引:0(有效,第0行存在);
(2)列索引:3(无效,因为最大列索引是2)。
由此可知,C选项的错误在于列索引越界(列索引3超出了0~2的范围),而不是"没有行索引"。
C选项有行索引(0),但列索引(3)越界了。
总结各选项:
A.
a[2][0]
→ 行索引2越界;B.
a[2][3]
→ 行索引2和列索引3都越界;C.
a[0][3]
→ 列索引3越界;D.
a[1>2][1]
→1>2
为假(0),即a[0][1]
,都有效。
只有D选项的索引都在有效范围内。
1.3 题目3
题干:在下面的字符数组定义中,哪一个有语法错误( )
A. char a[20] = "abcdefg"; B. char a[]="x + y = 5.";
C. char a[15]; D. char a[10] = '5';
解析:正确答案就是D选项。
我们来分析一下这个定义:
(1)这里试图用字符常量 '5'(类型为char)来初始化字符数组 a[10](类型为char[10])。
(2)在C语言中,字符数组的初始化必须使用字符串字面值(双引号),例如 char a[10]='' 5 '';,或者用花括号括起的字符列表,例如 char a[10] = {' 5 ',' ' 10 '};。
(3)直接使用字符常量(单引号)初始化字符数组是语法错误,因为类型不匹配(char 与 char[10])。
因此,选项D有语法错误。
其他选项:
A. char a[20]="abcdefg"; (正确);
B. char a[]="x+y=5."; (正确);
C. char a[15]; (正确,未初始化但语法合法)。
所以有语法错误的是D。
1.4 题目4
题干:下列定义数组的语句中正确的是( )【多选】
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];
解析:
A正确. 使用宏定义常量size(值为10),定义str1[size]和str2[size+2];,语法正确;
B错误. char str[]; 缺少数组大小(或初始化器),不完整类型,无法通过编译;
C正确. int num['10'];,'10'是多字符常量(实际值由实现定义),但类型为 int,可作为数组大小(合法,但可能警告);
D正确. C99支持变长数组(VLA),int n=5; int a[n][n+2]; 语法正确(前提是编译器支持C99及以上)。
因此,正确的是 A、C、D(B错误)。
但这里请大家注意:C选项(int num['10'];)中,'10' 是多字符常量(如ASCII中为'1'和'0'的组合值),但语法上允许作为整数常量表达式(尽管不常见);D选项需要C99支持。
1.5 题目5
题干:已知 i,j 都是整型变量,下列表达式中,与下标引用 X[ i ][ j ] 不等效的是()【多选】
A. *(X[ i ] + j) B. *(X + i)[ j ] C. *(X + i + j) D. *(*(X + i) + j)
解析:正确答案就是BC选项。
分析(假设X是二维数组,如int X[ M ][ N ] )——
A选项:二维数组X[ i ][ j ] 等价于*(*(X + i) + j) (D选项) ,也等价于:*(X[ i ] + j) (A选项,因为X[ i ]即
*(
X)
。B选项. *(X + i)[ j ] ,由于下标[ ]优先级高于*,等价于*(X + i)[ j ] ,即*(*(X + i + j))(错误,越界访问) ,与X[ i ][ j ] 不等效。
C选项:*(X + i + j)
等价于一维数组访问(如X[i + j]),而不是二维 (少了一层解引用) ,与X[ i ][ j ]不等效。D选项:*(*(X + i) + j)
,
正确等效。
因此,与X[ i ][ j ]不等效的是 B、C(A和D等效)。
选择题答案如下:
1.1 D
1.2 D
1.3 D
1.4 ACD
1.5 BC
校对一下,大家都做对了吗?
二、两道算法题
2.1 多数元素
力扣链接:169. 多数元素
力扣题解链接:摩尔投票法解决【多数元素】问题
题目描述:
2.1.1 题目理解
我们可以利用摩尔投票法来解决【多数元素】问题。
这道题是接口型的,下面是C语言的模版(如果是IO型就可以不用管它们了)——
2.1.2 思路
算法解释(摩尔投票法)
核心思想:
在数组中,多数元素的数量超过一半,所以它与其他元素"抵消"后,最终剩下的肯定是多数元素。
步骤:
1、初始化候选元素
candidate
为第一个元素,计数count
为1。2、遍历数组:
(1)如果
count == 0
,选择当前元素作为新的候选;(2)如果当前元素等于候选,
count++;
(3)如果当前元素不等于候选,
count--
。3、遍历结束后,
candidate
就是多数元素。博主用了自己的变量命名,为的是更加好理解,简化代码。
代码演示:
int majorityElement(int* nums, int numsSize) {if (numsSize == 0){return 0;}//多数元素int most_num = 0;//元素计数int count = 0;for (int i = 0; i < numsSize; i++){if (count == 0){most_num = nums[i];count = 1;}else if (nums[i] == most_num){count++;}else {count--;}}return most_num;
}
时间复杂度:O(n);
空间复杂度:O(1)。
我们学习了C++之后也可以尝试用C++来实现一下,看看自己前段时间C++学得怎么样——
代码演示:
//C++实现——摩尔投票法
class Solution {
public:int majorityElement(vector<int>& nums) {int candidate = nums[0];int count = 1;for (int i = 1; i < nums.size(); i++){if (count == 0){candidate = nums[i];count = 1;}else if (nums[i] == candidate){count++;}else{count--;}}return candidate;}
};
时间复杂度:O(n)
空间复杂度:O(1)
2.1.3 分治思想解决
分治算法解释:
分治思想——
1、分解:将数组分成左右两半;
2、解决:递归地在左右子数组中寻找多数元素;
3、合并:
(1)如果左右子数组的多数元素相同,直接返回;
(2)如果不同,统计两个候选元素在整个区间的出现次数,返回出现次数更多的那个。
时间复杂度:O(n log n)
(1)递归深度:log n;
(2)每层需要O(n)时间进行统计;
(3)总时间:O(n log n)。
空间复杂度:O(log n)(递归调用栈的深度)。
分治法的优势:
1、体现了分治思想:真正将问题分解为子问题;
2、易于理解:逻辑清晰,符合分治法的经典模式;
3、稳定性好:不依赖于特定的数据分布。
代码演示:
//C++实现——分治思想
class Solution {
public:int majorityElement(vector<int>& nums) {return majorityElementRecursive(nums, 0, nums.size() - 1);}private:int majorityElementRecursive(vector<int>& nums, int left, int right) {if (left == right) {return nums[left];}int mid = left + (right - left) / 2;int leftMajority = majorityElementRecursive(nums, left, mid);int rightMajority = majorityElementRecursive(nums, mid + 1, right);if (leftMajority == rightMajority) {return leftMajority;}int leftCount = countInRange(nums, left, right, leftMajority);int rightCount = countInRange(nums, left, right, rightMajority);return leftCount > rightCount ? leftMajority : rightMajority;}int countInRange(vector<int>& nums, int left, int right, int target) {int count = 0;for (int i = left; i <= right; i++) {if (nums[i] == target) {count++;}}return count;}
};
时间复杂度:O(nlogn),空间复杂度:O(logn)。
我们目前要写出来C++的写法是非常考验前面C++的学习情况好不好的,大家可以尝试去写一写,优先掌握C语言的写法,博主还没有介绍C++的算法题,之后会涉及的,敬请期待!
2.2 字符个数统计
牛客网链接:HJ10 字符个数统计
题目描述:
2.2.1 题目初步理解
题目是要求我们用哈希做的,但我们没学过哈希能不能做?当然可以做!
这道题是统计不同字符的个数,本质上就是去重计数,非常适合使用我们之前介绍过的处理“duplicate”的思路。不过我们不用duplicate写,当然用它写也是可以的,博主这里直接展示一下如果我们用这种思路写的写法——
思路:使用一个大小为 128 的数组(因为 ASCII码值 0-127 正好是 128 个)作为标记数组(或称为频率数组),来记录某个字符是否已经出现过。
1、初始化:
创建一个包含 128 个元素的数组(例如 char_set
),并将所有初始值设为 0(表示未出现)。
2、遍历字符串:
对于字符串中的每一个字符——
(1)将其转换为对应的 ASCII 码值(在 C 语言中,字符本身就是一个整数,可以直接用作数组索引);
(2)检查这个 ASCII 值是否在 [0, 127] 范围内(根据备注,输入其实都在 [33, 126],但按题目要求判断更严谨);
(3)如果该字符的 ASCII 码值
idx
在有效范围内,并且char_set[idx]
为 0(表示第一次遇到这个字符),则将计数器加 1,并标记char_set[idx] = 1
(表示这个字符已经出现过了,后续再遇到相同的字符就是 duplicate,不再计数)。
3、输出结果:
输出计数器的值。
代码演示:
#include <stdio.h>
#include <string.h>int main() {char str[501] = {0}; // 根据题目,最大长度500int char_set[128] = {0}; // 标记数组,初始化为0。索引0-127对应ASCII码int count = 0;int i = 0;int ascii_val;// 读取输入字符串fgets(str, sizeof(str), stdin);// 或者使用 gets(str); 但fgets更安全,它会保留换行符// 遍历字符串中的每个字符,直到遇到字符串结束符 '\0'while (str[i] != '\0') {// 获取当前字符的ASCII码值ascii_val = (int)str[i]; // 判断ASCII值是否在有效范围内 [0, 127]if (ascii_val >= 0 && ascii_val <= 127) {// 如果这个字符之前从未出现过 (char_set[ascii_val] == 0)if (char_set[ascii_val] == 0) {count++; // 计数器加1char_set[ascii_val] = 1; // 标记为已出现,后续重复的将不再计数}}i++;}printf("%d\n", count);return 0;
}
时间复杂度:O(n);
空间复杂度:O(1)。
2.2.2 题目深入理解
我们注意到题目备注说了“受限于输入,本题实际输入字符集为 ASCII 码在 33 到 126 范围内的可见字符”,所以空格(32)不用统计。
2.2.3 思路
1、只统计ASCII 0-127的字符;
2、相同的字符只算一次;
3、输入字符串可能包含各种字符。
这道题是IO型的,下面是C语言的模版(如果是IO型就可以不用管它们了)——
代码演示:
//C语言实现
#include <stdio.h>int main() {char s[501];int mark[128] = { 0 };int count = 0;fgets(s, sizeof(s), stdin);for (int i = 0; s[i] != '\0'; i++) {unsigned char c = s[i];// 根据题目备注,实际输入在33-126范围内if (c >= 33 && c <= 126) {if (mark[c] == 0) {mark[c] = 1;count++;}}}printf("%d\n", count);return 0;
}
时间复杂度:O(n);
空间复杂度:O(1)。
2.2.4 简化的C语言实现
代码演示:
//C语言自己实现——最终方案
#include <stdio.h>int main()
{char s[501];int num[128] = { 0 };int sum = 0;fgets(s, sizeof(s), stdin);for (int i = 0; s[i] != '\0'; i++) {unsigned char c = s[i];if (c >= 33 && c <= 126){if (num[c] == 0){num[c] = 1;sum++;}}}printf("%d\n", sum);return 0;
}
时间复杂度:O(n);
空间复杂度:O(1)。
2.2.5 C++实现
我们学习了C++之后也可以尝试用C++来实现一下,看看自己前段时间C++学得怎么样——
我们直接展示一下,C++的完整写法——
代码演示:
//C++实现
#include <iostream>
#include <string>
using namespace std;int main()
{string s;getline(cin, s); // 读取整行输入int mark[128] = { 0 }; // 标记数组,用于记录字符是否出现过int count = 0;for (char c : s){ // 遍历字符串中的每个字符unsigned char uc = c; // 转换为无符号字符if (uc < 128) { // 确保在0-127范围内if (mark[uc] == 0){ // 如果这个字符还没出现过mark[uc] = 1; // 标记为已出现count++; // 计数器增加}}}cout << count << endl;return 0;
}
时间复杂度:O(n),空间复杂度:O(1)。
2.2.6 简化C++实现
我们还是直接展示一下,C++简化后的写法——
代码演示:
//C++实现————简洁
#include <iostream>
#include <string>
using namespace std;int main()
{string s;getline(cin, s);int mark[128] = { 0 };int cnt = 0;for (char c : s){if ((unsigned char)c < 128 && mark[(unsigned char)c]++ == 0){cnt++;}}cout << cnt << endl;return 0;
}
时间复杂度:O(n),空间复杂度:O(1)。
我们目前要写出来C++的写法是非常考验前面C++的学习情况好不好的,大家可以尝试去写一写,优先掌握C语言的写法,博主还没有介绍C++的算法题,之后会涉及的,敬请期待!
结尾
本文内容到这里就全部结束了,希望大家练习一下这几道题目,这些基础题最好完全掌握!
往期回顾:
【C语言16天强化训练】从基础入门到进阶:Day 7
【C语言16天强化训练】从基础入门到进阶:Day 6
【C语言16天强化训练】从基础入门到进阶:Day 5
【C语言16天强化训练】从基础入门到进阶:Day 4
【C语言16天强化训练】从基础入门到进阶:Day 3
【C语言16天强化训练】从基础入门到进阶:Day 2
【C语言16天强化训练】从基础入门到进阶:Day 1
结语:感谢大家的阅读,记得给博主“一键四连”,感谢友友们的支持和鼓励!