当前位置: 首页 > news >正文

【C语言16天强化训练】从基础入门到进阶:Day 10


 🔥个人主页:艾莉丝努力练剑

❄专栏传送门:《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  负号标记法的巧妙之处

2.2.5  与其他方法的对比

结尾


一、五道选择题

1.1  题目1

题干:求函数返回值,传入 -1 ,则在64位机器上函数返回( )

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

题干:读代码选结果( )

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

题干:以下程序运行后的输出结果是( )

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次。

代码演示:

//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语言代码,包含了测试用例——

代码演示:

//完整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));      // 输出: 3printf("0 + 0 = %d\n", Add(0, 0));      // 输出: 0printf("5 + 7 = %d\n", Add(5, 7));      // 输出: 12printf("-1 + 1 = %d\n", Add(-1, 1));    // 输出: 0printf("10 + -5 = %d\n", Add(10, -5));  // 输出: 5return 0;
}

时间复杂度O(1)

空间复杂度O(1)

我们学习了C++之后也可以尝试用C++来实现一下,看看自己前段时间C++学得怎么样——

C++思路:

1、问题分析:题目要求不使用四则运算符号实现加法。我们可以使用位运算来模拟加法的过程。

2、关键观察:二进制加法的每一位可以分解为:

(1)非进位和:使用异或运算(^)得到,即 num1 ^ num2

(2)进位:使用与运算(&)并左移一位得到,即 (num1 & num2) << 1

3、迭代过程:将非进位和与进位相加,直到进位为0。每次迭代都更新非进位和和进位,直到没有进位为止。

代码演示:

//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型就可以不用管它们了)——

代码演示:

//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++学得怎么样——

代码演示:

//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:使用哈希表(不适用)

// 需要 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:排序后遍历(不最优)

// 时间复杂度 O(n log n),空间复杂度 O(1) 但修改了原数组
sort(nums.begin(), nums.end());
// 然后遍历检查缺失数字

方法3:负号标记法(最优)

1、时间复杂度: O(n) - 两次遍历;

2、空间复杂度: O(1) - 原地修改,无需额外空间;

3、保持信息: 通过负号标记,既记录了存在性,又保留了原始值的绝对值。


结尾

本文内容到这里就全部结束了,希望大家练习一下这几道题目,这些基础题最好完全掌握!

往期回顾:

【C语言16天强化训练】从基础入门到进阶:Day 9

【C语言16天强化训练】从基础入门到进阶:Day 8

【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

结语:感谢大家的阅读,记得给博主“一键四连”,感谢友友们的支持和鼓励!

http://www.dtcms.com/a/349528.html

相关文章:

  • CPLD与FPGA
  • 《Password Guessing Using Large Language Models》——论文阅读
  • 企业级Java项目整合ELK日志收集分析可视化
  • [论文阅读]RQ-RAG: Learning to Refine Queries for Retrieval Augmented Generation
  • 大模型知识--MCP
  • 无人机芯片休眠模式解析
  • Linux系统的网络管理(一)
  • 血缘元数据采集开放标准:OpenLineage Integrations Apache Spark Main Concepts Installation
  • 05 开发环境和远程仓库Gitlab准备
  • 【spring进阶】spring应用内方法调用时长统计
  • 【数据结构】串——(一)
  • 36 NoSQL 注入
  • Docker 部署 GitLab 并开启 SSH 使用详解
  • 【Java后端】Java 多线程:从原理到实战,再到高频面试题
  • Claude Code 使用及配置智能体
  • 【科研绘图系列】R语言绘制代谢物与临床表型相关性的森林图
  • 从零到一:现代化充电桩App的React前端参考
  • 将FGUI的Shader全部预热后,WebGL平台没有加载成功
  • 基于MalConv的恶意软件检测系统设计与实现
  • 大模型 transformer 步骤
  • 《拉康精神分析学中的欲望辩证法:能指的拓扑学与主体的解构性重构》
  • 计算机大数据技术不会?医院体检数据可视化分析系统Django+Vue全栈方案
  • 不止效率工具:AI 在文化创作中如何重构 “灵感逻辑”?
  • 【DFS 或 BFS 或拓扑排序 - LeetCode】329. 矩阵中的最长递增路径
  • 【图像算法 - 23】工业应用:基于深度学习YOLO12与OpenCV的仪器仪表智能识别系统
  • 基于视觉的果园无人机导航:一种基于干预模仿学习与VAE控制器的真实世界验证
  • 机器人中的李代数是什么
  • 抖音多账号运营新范式:巨推AI如何解锁流量矩阵的商业密码
  • 量子计算驱动的Python医疗诊断编程前沿展望(下)
  • 数据结构:单向链表的逆置;双向循环链表;栈,输出栈,销毁栈;顺序表和链表的区别和优缺点;0825