
❄专栏传送门:《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.1 题目1)
[1.2 题目2](#1.2 题目2)
[1.3 题目3](#1.3 题目3)
[1.4 题目4](#1.4 题目4)
[1.5 题目5](#1.5 题目5)
[2.1 数字在升序数组中出现的次数](#2.1 数字在升序数组中出现的次数)
[2.1.1 题目理解](#2.1.1 题目理解)
[2.1.2 思路](#2.1.2 思路)
[2.2 整数转换](#2.2 整数转换)
[2.2.1 题目理解](#2.2.1 题目理解)
[2.2.2 思路](#2.2.2 思路)
[2.2.3 报错](#2.2.3 报错)
[2.2.4 优化](#2.2.4 优化)
正文
一、五道选择题
1.1 题目1
**题干:**如下程序的功能是( )
cpp
#include <stdio.h>
int main()
{
char ch[80] = "123abcdEFG*&";
int j;
puts(ch);
for(j = 0; ch[j] != '\0'; j++)
if(ch[j] >= 'A' && ch[j] <= 'Z')
ch[j] = ch[j] + 'e' - 'E';
puts(ch);
return 0;
}
A. 测字符数组ch的长度 B. 将数字字符串ch转换成十进制数
C. 将字符数组ch中的小写字母转换成大写 D. 将字符数组ch中的大写字母转换成小写
解析: 循环遍历字符数组,遇到大写字母('A' ~ 'Z')时,通过ch[j] = ch[j] + 'e' - 'E';将其转换为小写(ASCII中 'e' - 'E' = 32,正好是大写转小写的差值)。
由此可知,答案是D。
1.2 题目2
**题干:**对于代码段,下面描述正确的是( )
cpp
t=0;
while(printf("*"))
{
t++;
if (t<3)
break;
}
A. 其中循环控制表达式与0等价 B. 其中循环控制表达式与'0'等价
C. 其中循环控制表达式是不合法的 D. 以上说法都不对
解析:printf("*")始终返回1(成功输出字符数),循环条件恒为真,但内部 t<3 时 break 会终止循环,因此控制表达式既不与0等价(非零为真),也不与'0'等价(ASCII值48,非零),也是合法的,故答案选D。
1.3 题目3
**题干:**以下程序运行时,若输入 1abcedf2df<回车> 输出结果是( )
cpp
#
include <stdio.h>
int main()
{
char ch;
while ((ch = getchar()) != '\n')
{
if (ch % 2 != 0 && (ch >= 'a' && ch <= 'z'))
ch = ch - 'a' + 'A';
putchar(ch);
}
printf("\n");
return 0;
}
A. 1abcedf2df B. 1ABCEDF2DF C. 1AbCEdf2df D. 1aBceDF2DF
**解析:**选项A正确。
题目所给的这段代码功能是:
将输入字符串中ASCII值为奇数的小写字母转为大写,其他字符不变,然后输出。
cpp
if (ch % 2 != 0 && (ch >= 'a' && ch <= 'z'))
ch = ch - 'a' + 'A';
(1)ch % 2 != 0 判断字符的ASCII值是否为奇数(即二进制最低位为1)。
(2)ch >= 'a' && ch <= 'z' 判断是否是小写字母。
满足条件时,将小写字母转换为大写(通过ASCII码差值计算:'a' - 'A' = 32,
但这里用的是 ch = ch - 'a' + 'A'; ,等价于 ch - 32)。
功能 :将输入字符串中ASCII值为奇数的小写字母转换为大写,其他字符不变。
例如:(1)输入 "abcde"(a=97奇, b=98偶, c=99奇, d=100偶, e=101奇);
(2)输出 "AbCdE"(仅奇数字母被转大写)。
1.4 题目4
**题干:**下列条件语句中,功能与其他语句不同的是( )
A. if(a) printf("%d\n",x); else printf("%d\n",y);
B. if(a==0) printf("%d\n",y); else printf("%d\n",x);
C. if (a!=0) printf("%d\n",x); else printf("%d\n",y);
D. if(a==0) printf("%d\n",x); else printf("%d\n",y);
**解析:**答案是D选项。
A、B、C均为a非零时输出x,a为零时输出y;而D是a为零时输出x,a非零时输出y,功能相反。
1.5 题目5
**题干:**我们知道C语言的 break 语句只能跳出离它最近的一层循环,可是有时候我们需要跳出多层循环,下列跳出多层循环的做法正确的是( )【多选】
A. 将程序写成函数用return结束函数,便可跳出循环
B. 修改外层循环条件例如:
cpp
for( int i = 0 ; i < MAX1 ; i ++ )
{
for( int j = 0 ; j < MAX2 ; j ++ )
{
if( condition )
{
i = MAX1;
break;
}
}
}
C. 在外层循环设置判断条件例如:
cpp
or( ; symbol != 1 && condition2 ; )
{
for( ; symbol != 1 && condition3 ; )
{
if( condition1 )
symbol = 1 ;
}
}
D. 在外层循环后面加入break例如:
cpp
for( ; condition2 ; )
{
for( ; condition3 ; )
{
if( condition1 )
symbol = 1 ;
}
if(symbol == 1 )
break ;
}
**解析:**答案是ABCD。
我们分别来看这四个选项------
A选项: return直接结束函数,跳出所有循环。
**B选项:**内层break跳出内层循环,同时修改外层循环变量i迫使外层循环结束。
C选项: 通过符号变量symbol控制外层循环条件,条件不满足时终止循环。
**D选项:**内层修改symbol后,外层循环通过break终止。
所有选项均能实现跳出多层循环,答案已经有了。
选择题答案如下:
1.1 D
1.2 D
1.3 A
1.4 D
1.5 ABCD
校对一下,大家都做对了吗?
二、两道算法题
2.1 数字在升序数组中出现的次数
牛客链接:JZ53 数字在升序数组中出现的次数
题目描述:

2.1.1 题目理解
这道题既然说了长度为n的非降序数组(升序数组)、非负数整数k(0或正整数),而且要我们统计k(常见的叫法是val,这里叫什么都无所谓)出现的次数,也就是指定的一个数在这个长度为n的非降序数组中一共出现了几次,很简单,题目提示了这道题考查的是二分法。
这道题是接口型的,下面是C语言的模版(如果是IO型就可以不用管它们了)------

我们不管三七二十一,先判断有效长度,nunsLen等于0,直接return 0。
2.1.2 思路
我们的思路就是,两次二分查找,左边界查一遍,比k小,left就mid+1,比k大,right就mid-1;右边界加上nums[mid]刚好等于我们要找的k这个范围,同样------比k小或者等于k,left就mid+1,比k大,right就mid-1。因为是接口型,我们不用自己输入输出,直接return我们的结果------左边界和右边界二分查找完,我们定义一个新变量,返回就返回右边界-左边界+1,就是我们要的数字在升序数组中出现的次数。
代码演示:
cpp
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param nums int整型一维数组
* @param numsLen int nums数组长度
* @param k int整型
* @return int整型
*/
int GetNumberOfK(int* nums, int numsLen, int k) {
// write code here
if (numsLen == 0) {
return 0;
}
//进行两次二分查找 时间复杂度:O(logn)
//左边界
int left = 0;
int right = numsLen - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < k) {
left = mid + 1;
}
else {
right = mid - 1;
}
}
int left_bound = left;
//右边界
left = 0;
right = numsLen - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] <= k) {
left = mid + 1;
}
else {
right = mid - 1;
}
}
int right_bound = right;
return right_bound - left_bound + 1;
}
这里博主用了两次二分查找,空间上也只是调用了常数次的变量,所以复杂度良好------
时间复杂度 :O(logn);
空间复杂度 :O(1)。
我们学习了C++之后也可以尝试用C++来实现一下,看看自己前段时间C++学得怎么样------

代码演示:
cpp
//C++的实现
class Solution {
public:
int GetNumberOfK(vector<int>& nums, int k) {
if (nums.empty()) return 0;
int left = 0, right = nums.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < k) {
left = mid + 1;
}
else {
right = mid - 1;
}
}
int left_nums = left;
left = 0, right = nums.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] <= k) {
left = mid + 1;
}
else {
right = mid - 1;
}
}
int right_nums = right;
return right_nums - left_nums + 1;
}
};
时间复杂度: O(logn)****,空间复杂度: O(1)****。
我们目前要写出来C++的写法是非常考验前面C++的学习情况,大家可以尝试去写一写,优先掌握C语言的写法,博主还没有介绍C++的算法题,之后会涉及的,敬请期待!
2.2 整数转换
力扣链接:面试题 05.06. 整数转换
力扣题解链接:用异或、右移操作轻松搞定【整数转换】问题
题目描述:

2.2.1 题目理解
像整数转换这种题目,我们要联系前面学过的异或、按位与等知识点。要解决这个问题,我们需要计算将整数A转换为整数B所需改变的位数。这实际上就是计算A和B的二进制表示中有多少位是不同的。最直接的方法就是使用异或操作:异或操作的结果中,每一位为1表示该位在A和B中不同,为0则表示相同。因此,我们只需要计算A异或B的结果中1的个数即可。
2.2.2 思路
我们的思路主要就是围绕三点:"1"的统计、异或的操作、负数的处理------
(1)异或操作:首先对A和B进行异或操作,得到一个整数C,其中每一个为1的位表示A和B在该位上不同。
(2)统计1的个数:然后统计C中1的个数,这个数就是需要改变的位数。
(3)处理负数:由于整数可能为负数,而C语言中负数的右移操作是算术右移(会填充符号位,1是负数符号位、0是正数符号位),这可能导致无限循环。因此,我们使用无符号整数来避免这个问题。具体来说,将异或结果转换为无符号整数后再进行位统计。
还有一点很重要,我们得使用无符号整数------
cpp
unsigned int val = (unsigned int)(A ^ B);
使用无符号数很重要,因为:
1)对于有符号数 ,右移是算术右移 (用符号位填充左边);
2)对于无符号数 ,右移是逻辑右移 (用0填充左边)。
如果是负数使用算术右移,可能会导致无限循环!
cpp
int signed_val = -1; // 二进制: 11111111 11111111 11111111 11111111
unsigned int unsigned_val = (unsigned int)-1; // 同上
signed_val >>= 1; // 结果还是 -1 (算术右移,符号位填充)
unsigned_val >>= 1; // 结果变成 2147483647 (逻辑右移,0填充)
这样我们就把负数给处理好了。
这道题是接口型的,下面是C语言的模版(如果是IO型就可以不用管它们了)------

2.2.3 报错
代码演示:
我们有了思路,直接开始写代码,有两种写法------
cpp
int convertInteger(int A, int B) {
int count = 0;
unsigned int val = (unsigned int)(A ^ B);
while (val != 0)
{
count += val & 1;
}
return count;
}
还有这种------
cpp
//超时了
int convertInteger(int A, int B) {
int count = 0;
unsigned int val = (unsigned int)(A ^ B);
while (val != 0)
{
if (val & 1)
{
count++;
}
}
return count;
}
时间复杂度 :O(1);
空间复杂度 :O(1)。
可是一调试,坏了:【超出时间限制】------

2.2.4 优化
我们仔细想一下,思路是没问题的,怎么样给它优化一下呢?
之前写轮转数组那道题的时候博主就展示了三种写法,最后是用数组逆置解决的,一开始也是提示【超出时间限制】,之前是因为力扣的通过比较严格:有几十万个测试用例,才通不过的,这道题目我们看到**【超出时间限制】一定要条件反射------是不是无限循环****了?**
我们发现,原本我们只是单纯地统计"1"的数量,没有右移操作!
如果没有右移操作,代码会变成:
cpp
// 错误代码 ------ 会导致无限循环!
while (val != 0) {
count += val & 1;
// 缺少 val >>= 1;
}
后果就是------
(1)无限循环 :val 的值永远不会改变;
(2)永远检查同一个位 :每次都只检查最低位;
(3)程序卡死 :无法退出循环,程序卡死。
那么我们加上右移操作,很有可能就能解决问题了!
val >>= 1; 是右移赋值操作,相当于 val = val >> 1;。
**val >>= 1;**的作用是:
1、逐位处理:每次右移一位,让下一个位成为最低位;
2、遍历所有位:通过循环右移,可以检查数值的所有二进制位;
3、配合 &1:每次检查当前的最低位是否为1;
4、确保正确性:使用无符号数避免算术右移的问题。
这样就能准确统计出异或结果中1的个数,即A和B不同的位数!
代码演示:
cpp
//优化
int convertInteger(int A, int B) {
int count = 0;
unsigned int val = (unsigned int)(A ^ B);
while (val != 0)
{
count += val & 1;
val >>= 1;//避免无限循环
}
return count;
}
时间复杂度 :O(1);
空间复杂度 :O(1)。
我们再调试一下,发现代码顺利通过了!

我们发现,增加val >>= 1;这个式子似乎让原本【超出时间限制】的问题被优化了?!
是的。****增加val >>= 1;这个操作是避免无限循环的关键优化!
**为什么****增加val >>= 1;**能做到优化?
(1)逐位处理机制:
cpp
xor_val = 18; // 二进制: 10010
循环1: 检查 10010 的最低位 (0) → 右移 → 1001
循环2: 检查 1001 的最低位 (1) → 右移 → 100
循环3: 检查 100 的最低位 (0) → 右移 → 10
循环4: 检查 10 的最低位 (0) → 右移 → 1
循环5: 检查 1 的最低位 (1) → 右移 → 0 → 退出循环
(2)确保循环终止: 每次右移都让数值变小(或者至少位数减少),最终会变成0------
cpp
10010 (18) → 1001 (9) → 100 (4) → 10 (2) → 1 (1) → 0
(3)优化时间复杂度: 对于32位整数,最多也只需要32次循环****------
cpp
// 最坏情况:所有位都是1
val = 0xFFFFFFFF; // 32个1
// 需要32次右移才能变成0
// 每次循环都是O(1)操作,总时间复杂度O(32)=O(1)
既然是常数,那就是时间复杂度就是O(1)。
由此可见,右移操作有以下几个好处------
(1)避免死循环:没有右移就会卡死;
(2)确保正确性:能遍历所有位;
(3)控制时间复杂度:固定32次循环;
(4)通过测试用例:否则会"超出时间限制"。
我们学习了C++之后也可以尝试用C++来实现一下,看看自己前段时间C++学得怎么样------

代码演示:
cpp
class Solution {
public:
int convertInteger(int A, int B) {
unsigned int xor_val = (unsigned int)(A ^ B);
int count = 0;
while (xor_val != 0) {
count += xor_val & 1;
xor_val >>= 1;
}
return count;
}
};
时间复杂度: O(1)****,空间复杂度: O(1)****。
我们可以进阶一下,使用内置函数能不能解决?甚至更加简单!
代码演示:
cpp
class Solution {
public:
int convertInteger(int A, int B) {
return __builtin_popcount(A ^ B);
}
};
时间复杂度: O(1)****,空间复杂度: O(1)****。

是不是直接薄纱?哈哈哈,C++有时候就是这样,所以我们学习了C++再去刷题,会很"有趣"。
我们目前要写出来C++的写法是非常考验前面C++的学习情况,大家可以尝试去写一写,优先掌握C语言的写法,博主还没有介绍C++的算法题,之后会涉及的,敬请期待!
结尾
本文内容到这里就全部结束了,希望大家练习一下这几道题目,这些基础题最好完全掌握!
往期回顾:
结语: 感谢大家的阅读,记得给博主"一键四连",感谢友友们的支持和鼓励!