
❄专栏传送门:《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 负号标记法的巧妙之处)
[2.2.5 与其他方法的对比](#2.2.5 与其他方法的对比)
一、五道选择题
1.1 题目1
**题干:**求函数返回值,传入 -1 ,则在64位机器上函数返回( )
cpp
int func(int x)
{
int count = 0;
while (x)
{
count++;
x = x&(x - 1);//与运算
}
return count;
}
A. 死循环 B. 64 C. 32 D. 16
解析:正确答案就是B选项。
函数 func
的作用是计算整数 x
的二进制表示中1的个数(即汉明重量)。通过循环 x = x & (x - 1)
每次会消除二进制表示中最右边的1,直到 x
变为0。
传入 -1:在64位机器上,-1 的二进制表示是所有位都是1(即64个1)。因此,循环会执行64次(每次消除一个1),最后返回 count = 64。
1.2 题目2
**题干:**读代码选结果( )
cpp
int count = 0;
int x = -1;
while(x)
{
count++;
x = x >> 1;
}
printf("%d",count)
A. 1 B. 2 C. 32 D. 死循环,没结果
解析:正确答案就是D选项。
这里 x 是带符号整数(默认是 signed int),初始为 -1(二进制所有位为1)。执行右移操作 x = x >> 1 是算术右移(对于负数,高位补1)。因此,无论右移多少次,x 始终不会变为0(因为高位一直补1,保持所有位为1,即始终为-1)。所以循环条件 x 永远为非0,导致死循环。
1.3 题目3
**题干:**下述赋值语句错误的是( )
A. a = (b = (c = 2 , d = 3)) B. i++ C. a / b = 2 D. a = a < a + 1
解析:正确答案就是C选项。
A. 正确,逗号表达式返回最后一个值(d=3),然后赋值给b,再赋值给a;
B. 正确,自增操作;
C. 错误,a/b是一个表达式(值),不能作为左值被赋值;
D. 正确,先计算a < a+1(恒为真,即1),然后赋值给a。
1.4 题目4
**题干:**若有 int w=1, x=2, y=3, z=4; 则条件表达 w < x ? w : y < z ? y : z 的值是( )
A. 1 B. 2 C. 3 D. 4
解析:正确答案就是A选项。
题干所给表达式:w < x ? w : y < z ? y : z
结合性:从右到左(条件运算符是右结合),所以等价于:w < x ? w : (y < z ? y : z)**计算:**w=1, x=2 -> w<x 为真(1),所以返回 w(即1)
因此w < x ? w : y < z ? y : z的值为1。
1.5 题目5
**题干:**以下程序运行后的输出结果是( )
cpp
int main()
{
int a=1,b=2,m=0,n=0,k;
k=(n=b<a)&&(m=a);
printf("%d,%d\n",k,m);
return 0;
}
A. 0,0 B. 0,1 C. 1,0 D. 1,1
解析:正确答案就是A选项。
(1)先计算 n = b<a(即n=2<1,为假,所以n=0);
(2)由于 && 短路:第一个操作数 (n=b<a) 结果为0(假),所以第二个表达式 (m=a) 不会执行(m保持原值0);
(3)k 为整个逻辑与的结果(0)。
因此输出:k=0, m=0。
选择题答案如下:
1.1 B
1.2 D
1.3 C
1.4 A
1.5 A
校对一下,大家都做对了吗?
二、两道算法题
2.1 不用加减乘除做加法
牛客链接:JZ65 不用加减乘除做加法
题目描述:

2.1.1 题目理解
为了解决这个问题,我们需要在不使用加减乘除运算符的情况下实现两个整数的加法。我们可以利用位运算来模拟加法过程。
这道题是接口型的,下面是C语言的模版(如果是IO型就可以不用管它们了)------

2.1.2 思路
C语言思路:
1、算法原理:使用位运算模拟二进制加法。
(1)异或运算(~):得到不考虑进位的和;
(2)与运算 + 左移(& + << 1):得到进位值。
2、迭代过程:
(1)每次循环计算当前位的和(不考虑进位)和进位值;
(2)将和赋值给 num1,进位赋值给 num2;
(3)重复直到进位为0。
3、处理负数:由于C语言中使用补码表示负数,该算法同样适用于负数加法。
4、时间复杂度:O(1),因为整数位数固定(通常是32位),最多循环32次。
代码演示:
cpp
//C语言实现
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param num1 int整型
* @param num2 int整型
* @return int整型
*/
int Add(int num1, int num2)
{
while (num2 != 0)
{
int sum = num1 ^ num2;
int carry = (num1 & num2) << 1;
num1 = sum;
num2 = carry;
}
return num1;
}
时间复杂度 :O(1);
空间复杂度 :O(1)。
博主这里再展示一下完整的C语言代码,包含了测试用例------
代码演示:
cpp
//完整C语言代码
#include <stdio.h>
int Add(int num1, int num2)
{
while (num2 != 0)
{
int sum = num1 ^ num2;
int carry = (num1 & num2) << 1;
num1 = sum;
num2 = carry;
}
return num1;
}
int main()
{
// 测试用例
printf("1 + 2 = %d\n", Add(1, 2)); // 输出: 3
printf("0 + 0 = %d\n", Add(0, 0)); // 输出: 0
printf("5 + 7 = %d\n", Add(5, 7)); // 输出: 12
printf("-1 + 1 = %d\n", Add(-1, 1)); // 输出: 0
printf("10 + -5 = %d\n", Add(10, -5)); // 输出: 5
return 0;
}
时间复杂度 :O(1);
空间复杂度 :O(1)。
我们学习了C++之后也可以尝试用C++来实现一下,看看自己前段时间C++学得怎么样------

C++思路:
1、问题分析:题目要求不使用四则运算符号实现加法。我们可以使用位运算来模拟加法的过程。
2、关键观察:二进制加法的每一位可以分解为:
(1)非进位和 :使用异或运算(^)得到,即 num1 ^ num2;
(2)进位 :使用与运算(&)并左移一位得到,即 (num1 & num2) << 1。
3、迭代过程:将非进位和与进位相加,直到进位为0。每次迭代都更新非进位和和进位,直到没有进位为止。
代码演示:
cpp
//C++实现
class Solution {
public:
int Add(int num1, int num2)
{
while (num2 != 0)
{
int sum = num1 ^ num2;
int carry = (num1 & num2) << 1;
num1 = sum;
num2 = carry;
}
return num1;
}
};
时间复杂度:O(1),空间复杂度: O(1)****。
1、循环条件 :当进位(num2)不为0时,继续循环。2、计算非进位和 :使用异或运算 num1 ^ num2 得到当前位的和,不考虑进位。
3、计算进位 :使用与运算 num1 & num2 并左移一位得到进位。
4、更新变量 :将非进位和赋值给 num1,进位赋值给 num2,进行下一次迭代。
5、返回结果 :当进位为0时,num1 即为最终的和,返回 num1。
使用位运算模拟二进制加法通过位运算高效地模拟了加法过程,避免了使用四则运算符号。
我们目前要写出来C++的写法是非常考验前面C++的学习情况好不好的,大家可以尝试去写一写,优先掌握C语言的写法,博主还没有介绍C++的算法题,之后会涉及的,敬请期待!
2.2 找到所有数组中消失的数字
力扣链接:448. 找到所有数组中消失的数字
力扣题解链接:负号标记法解决【找到所有数组中消失的数字】问题
题目描述:

2.2.1 题目理解
题目的本质是:在一个长度为
n
的数组中,所有数字理论上都应该在[1, n]
范围内,但由于某些数字可能重复出现,导致其他数字缺失。我们需要找出所有缺失的数字。
本题中关键的约束条件------
1、数组长度 = n;
2、数字范围 = [1, n];
3、可能有重复数字;
4、需要找出所有缺失的数字。
2.2.2 思路
我们使用负号标记法,时间复杂度为 O(n),空间复杂度为 O(1)(不考虑输出数组):
1、第一次遍历 :对于每个数字 nums[i]
,我们将 nums[abs(nums[i])-1]
标记为负数;
2、第二次遍历 :所有仍然为正数的位置 i
表示数字 i+1
在原始数组中不存在。
这道题是接口型的,下面是C语言的模版(如果是IO型就可以不用管它们了)------

代码演示:
cpp
//C语言实现------负号标记法
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* findDisappearedNumbers(int* nums, int numsSize, int* returnSize) {
// 第一次遍历:使用负号标记法
for (int i = 0; i < numsSize; i++)
{
int index = abs(nums[i]) - 1; // 获取对应的索引位置
if (nums[index] > 0)
{
nums[index] = -nums[index]; // 标记为负数表示该数字存在
}
}
// 统计缺失的数字数量
int count = 0;
for (int i = 0; i < numsSize; i++)
{
if (nums[i] > 0)
{
count++;
}
}
// 分配结果数组
int* result = (int*)malloc(count * sizeof(int));
*returnSize = count;
// 第二次遍历:收集缺失的数字
int j = 0;
for (int i = 0; i < numsSize; i++)
{
if (nums[i] > 0)
{
result[j++] = i + 1; // 索引+1就是缺失的数字
}
}
return result;
}
时间复杂度 :O(n);
空间复杂度 :O(1)。
我们学习了C++之后也可以尝试用C++来实现一下,看看自己前段时间C++学得怎么样------

代码演示:
cpp
//C++实现------负号标记法
class Solution {
public:
vector<int> findDisappearedNumbers(vector<int>& nums)
{
int n = nums.size();
// 使用负号标记法
for (int i = 0; i < n; i++)
{
int index = abs(nums[i]) - 1; // 获取对应的索引位置
if (nums[index] > 0)
{
nums[index] = -nums[index]; // 标记为负数表示该数字存在
}
}
vector<int> result;
// 收集缺失的数字
for (int i = 0; i < n; i++)
{
if (nums[i] > 0)
{
result.push_back(i + 1); // 索引+1就是缺失的数字
}
}
return result;
}
};
时间复杂度: O(n)****,空间复杂度: O(1)****。
我们目前要写出来C++的写法是非常考验前面C++的学习情况好不好的,大家可以尝试去写一写,优先掌握C语言的写法,博主还没有介绍C++的算法题,之后会涉及的,敬请期待!
我们可以用几个示例来验证一下------
对于输入 [4, 3, 2, 7, 8, 2, 3, 1]
:
(1)第一次遍历后数组变为:[-4, -3, -2, -7, 8, 2, -3, -1];
(2)位置4和5(索引从0开始)的值仍为正数,所以缺失的数字是5和6。
负号标记法这种方法高效且不需要额外的空间(除了输出数组),非常适合处理大规模数据。
2.2.3 为什么选择负号标记法?
1、空间效率: 题目要求 O(1) 额外空间(不包括输出数组);
2、时间效率: 需要 O(n) 时间复杂度;
3、利用现有数组: 既然数字范围是 [1, n],我们可以用数组索引本身来记录信息。
2.2.4 负号标记法的巧妙之处
核心思想:用数组的索引位置来记录数字是否存在。
具体操作:
(1)对于数字 x,我们去查看位置 x-1;
(2)如果该位置的值为正,我们就将其标记为负;
(3)这样,最后仍然为正数的位置 i 就表示数字 i+1 不存在。
为什么这样可行?
(1)因为数组索引从 0 到 n-1,正好对应数字 1 到 n;
(2)负号标记不会改变绝对值,所以不影响后续的判断。
2.2.5 与其他方法的对比
方法1:使用哈希表(不适用)
cpp
// 需要 O(n) 额外空间,不符合要求
unordered_set<int> seen;
for (int num : nums) seen.insert(num);
for (int i = 1; i <= n; i++)
{
if (!seen.count(i)) result.push_back(i);
}
方法2:排序后遍历(不最优)
cpp
// 时间复杂度 O(n log n),空间复杂度 O(1) 但修改了原数组
sort(nums.begin(), nums.end());
// 然后遍历检查缺失数字
方法3:负号标记法(最优)
1、时间复杂度 : O(n) - 两次遍历;
2、空间复杂度 : O(1) - 原地修改,无需额外空间;
3、保持信息 : 通过负号标记,既记录了存在性,又保留了原始值的绝对值。
结尾
本文内容到这里就全部结束了,希望大家练习一下这几道题目,这些基础题最好完全掌握!
往期回顾:
结语: 感谢大家的阅读,记得给博主"一键四连",感谢友友们的支持和鼓励!