C++ 位运算 高频面试考点 力扣 371. 两整数之和 题解 每日一题
文章目录
- 题目解析
- 为什么这道题值得你花几分钟弄懂?
- 这道题的核心解法:位运算模拟加法
- 二进制加法的本质
- 完整位运算解法
- 其他思路对比(仅作思路扩展)
- 利用数组下标(仅限非负数)
- 利用栈的深度(仅限非负数)
- 扩展思考:位运算解法与计算机底层加法逻辑的关联
- 本题解法如何复刻底层逻辑?
- 面试避坑指南
- 思考题
- 下题预告


题目解析
题目链接:力扣 371. 两整数之和
题目描述:
示例 1:
输入:a = 1, b = 2
输出:3
示例 2:
输入:a = 2, b = 3
输出:5
限制:
- -1000 <= a, b <= 1000
为什么这道题值得你花几分钟弄懂?
这道题是打破“常规运算思维”的经典题目——它强制我们跳出“直接用 +
-
号计算”的惯性,深入理解计算机底层的二进制加法逻辑。其核心价值不在于“算出结果”,而在于掌握“位运算模拟算术运算”的核心思路,这是面试中考察“底层逻辑理解能力”的高频考点。
面试官考察这道题时,核心关注三点:
- 能否联想到“二进制加法拆分”,将求和拆分为“无进位加法”和“进位处理”两部分,体现对加法本质的理解;
- 能否用位运算分别实现“无进位加法”和“进位计算”,暴露对位运算特性的掌握程度;
- 能否处理“负数进位”的边界问题(即为何要用
unsigned int
),彰显对计算机中“补码存储”的细节认知。
如果对位运算的基础特性(如异或、与、左移)有些生疏,建议先回顾位运算的核心规则:异或(^
)可模拟“无进位相加”,与(&
)+ 左移(<<1
)可模拟“进位计算”,这是解决本题的核心工具。可以根据我的这篇博客进行理解回忆 位运算 常见方法总结 算法练习 C++
这道题的核心解法:位运算模拟加法
由于题目禁止使用 +
-
运算符,我们必须回归加法的本质——二进制加法。在二进制中,两个数的加法可拆分为“无进位加法结果”和“进位值”两部分,重复处理这两部分直到无进位,最终结果即为总和。
二进制加法的本质
以十进制加法 13 + 9 = 22
为例,其二进制表示为 1101 + 1001 = 10110
。我们可将其拆分为两步:
-
无进位加法(即异或):异或的本质就是无进位相加,即不考虑进位,对应位直接相加(0+0=0,0+1=1,1+1=0),结果为
0100
(即十进制的 4);
-
计算进位:仅保留产生进位的位(只有 1+1 会产生进位,进位值向左移 1 位),即我们可以用按位与统计哪个位要进位,在左移一位进行进位操作,结果为
10010
(即十进制的 18);
-
重复处理:将“无进位结果”与“进位值”再次执行上述两步,直到进位值为 0。此时无进位结果即为最终总和:
第二次无进位加法:0100 + 10010 = 10110
(无进位),进位值为 0,最终结果为10110
(十进制 22)。
位运算如何实现这两步?
我们可以用三种位运算分别实现二进制加法的核心操作:
操作需求 | 位运算实现 | 原理说明 |
---|---|---|
无进位加法 | a ^ b | 异或的特性:相同为 0(1^1=0),不同为 1(0^1=1),完全匹配“无进位相加”规则 |
计算进位值 | (a & b) << 1 | 1. 与运算(a&b ):仅当对应位均为 1 时结果为 1,标记出“需要进位的位”;2. 左移 1 位( <<1 ):进位值需传递到下一位,符合加法进位规则 |
完整位运算解法
基于上述原理,我们可以设计一个循环:不断用“无进位结果”更新 a
,用“进位值”更新 b
,直到 b
为 0(无进位),此时 a
即为最终总和。
1. 代码实现
class Solution {
public:int getSum(int a, int b) {while(b != 0) // 当进位值为0时,循环结束,a即为结果{// 1. 计算无进位加法结果int sum = a ^ b;// 2. 计算进位值:用unsigned int避免负数进位的符号位问题unsigned int carry = (unsigned int)(a & b) << 1;// 3. 更新a为无进位结果,b为进位值,进入下一轮循环a = sum;b = carry;}return a;}
};
2. 关键解析:为什么要用 unsigned int
?
这是本题最容易被忽略的细节,也是面试中的高频考点——解决负数进位时的“符号位扩展”问题。
背景:计算机中负数的存储方式
计算机用“补码”存储负数,最高位(符号位)为 1 表示负数。例如在 32 位系统中:
- 十进制
-1
的二进制补码为11111111 11111111 11111111 11111111
(符号位为 1,其余位全 1); - 当对负数执行左移操作(
<<1
)时,若不处理符号位,编译器会进行“符号位扩展”(即保持符号位不变,填充 1 而非 0),导致进位值计算错误。
问题演示:若不用 unsigned int
会怎样?
假设 a = -1
(补码 11111111
),b = -1
(补码 11111111
):
- 计算
a & b
:结果为11111111
(符号位为 1,表示负数); - 直接左移 1 位(
(a&b) << 1
):由于符号位扩展,结果会变成11111110
(仍为负数),而非正确的进位值11111110
(作为无符号数时表示十进制 254,即进位值应传递的正确值); - 最终导致循环无法终止,或计算结果错误。
unsigned int
的作用
unsigned int
(无符号整数)会强制编译器将变量视为“无符号数”,完全忽略符号位,左移时仅在右侧填充 0,避免符号位扩展导致的进位错误。即使原始 a
和 b
是负数,通过 (unsigned int)(a & b)
转换后,也能正确计算进位值的二进制表示,确保循环正常终止。
3. 解法优劣势分析
优点 | 缺点 |
---|---|
✅ 完全符合题目要求:不使用 + - 运算符 | ⚠️ 逻辑依赖二进制基础:需理解补码、位运算特性,对新手不友好 |
✅ 时间复杂度 O(1):循环次数最多为整数的位数(如 32 位系统最多 32 次),可视为常数时间 | ⚠️ 需处理符号位细节:必须用 unsigned int 避免负数进位错误,否则会出 bug |
✅ 空间复杂度 O(1):仅用几个变量存储中间结果,无额外空间开销 | |
✅ 通用性强:适用于正负数、零的所有组合,无边界限制 |
注:每次进位至少将最高位的1左移一位,而32位整数的符号位占1位,因此最多左移31次后,进位值会超出表示范围而“自然溢出”为0,故循环次数不超过32次。
其他思路对比(仅作思路扩展)
虽然本题的最优解是位运算,但为了更全面地理解“禁止 +
-
时的求和方式”,以下两种思路可作为对比(实际面试中不推荐,效率或通用性差):
利用数组下标(仅限非负数)
核心思路:创建一个长度为 a+1
的数组,再通过数组下标访问 b
次,最终下标即为 a+b
。
局限性:
- 无法处理负数(数组下标不能为负);
- 当
a
或b
较大时(如 1000),数组会占用大量冗余空间,效率极低。
代码示例:
class Solution {
public:int getSum(int a, int b) {// 仅适用于a、b为非负数的情况if (a < 0 || b < 0) return -1; // 负数处理不了char arr[a + 1]; // 创建长度为a+1的数组return (int)(&arr[b] - arr); // 下标差即为a+b}
};
利用栈的深度(仅限非负数)
核心思路:通过递归调用栈的深度模拟加法,递归 a
次后再递归 b
次,栈的深度即为总和。
局限性:
- 无法处理负数;
- 递归深度过大时(如
a+b=1000
)会触发栈溢出,程序崩溃。
代码示例:
class Solution {
public:int getSum(int a, int b) {if (b == 0) return a;// 递归一次相当于b减1,a加1,直到b为0return getSum(a + 1, b - 1); }
};
扩展思考:位运算解法与计算机底层加法逻辑的关联
我们必须先明确一个核心事实:我们的位运算解法,本质上是对计算机底层处理+
和-
运算的逻辑复刻。计算机硬件层面并不直接识别“+”和“-”这类符号,而是通过补码编码与全加器电路,将加减法统一为二进制位运算与进位处理,而这正是本题解法的设计依据。
计算机底层如何处理+
和-
?
计算机处理加减法的核心机制可概括为"一套电路、两种运算":
-
存储基础:补码编码
- 计算机中所有整数(包括正数、负数)都以"补码"形式存储,这是实现加减统一的关键。
- 正数补码=原码(符号位为0,数值位为二进制绝对值)
- 负数补码=绝对值的反码+1(符号位为1,确保减法可转化为加法)
- 举例:8位系统中,
-3
的补码是11111101
(3的原码00000011
→反码11111100
→+1→11111101
)
-
加法实现:全加器电路
- 硬件核心是"全加器"电路,它能处理两个二进制位的相加并考虑低位进位:
- 本位和 = 被加数 ^ 加数 ^ 低位进位(异或运算实现无进位相加)
- 向高位进位 = (被加数 & 加数) | (被加数 & 低位进位) | (加数 & 低位进位)(与运算识别进位位)
- 32位整数加法通过32个全加器串联实现,低位进位逐位传递到高位,直到最高位
- 硬件核心是"全加器"电路,它能处理两个二进制位的相加并考虑低位进位:
-
减法实现:转化为加法
- 计算机没有专门的减法器,
a - b
会被自动转换为a + (-b)
-b
通过"对b的补码取反加1"获得(与补码的定义一致)- 最终仍通过加法器完成运算,实现了"一套硬件电路处理两种运算"
- 计算机没有专门的减法器,
本题解法如何复刻底层逻辑?
我们的位运算解法与计算机底层加法逻辑形成了完美映射:
计算机底层机制 | 本题位运算实现 | 对应代码 |
---|---|---|
无进位加法计算 | 异或运算 a ^ b | sum = a ^ b |
进位值计算 | 与运算+左移 (a & b) << 1 | carry = (unsigned int)(a & b) << 1 |
进位传递循环 | 循环处理进位直到无进位 | while(b != 0) { ... } |
补码处理负数 | 直接对补码执行位运算 | 无需额外处理,直接操作a 和b |
避免符号位扩展 | 硬件自动按无符号处理进位 | unsigned int 强制无符号左移 |
以-1 + 2
的计算为例:
- 底层:
-1
的补码11111111
与2
的补码00000010
通过全加器逐位计算 - 本题:
a ^ b
得到无进位结果11111101
(-3),(a & b) << 1
得到进位00000100
(4),循环处理直到进位为0
这种对应关系揭示了一个重要事实:本题要求"不用+和-实现求和",本质上是让我们手动实现一次计算机硬件的加法过程。我们用软件代码模拟了硬件电路的逻辑,这就是解法的根本依据。
理解了这层关联,不仅能掌握本题解法,更能触类旁通——所有位运算模拟算术运算的题目(如乘法、除法),其本质都是对计算机底层运算逻辑的软件复现。
面试避坑指南
- 忘记处理负数进位:若不用
unsigned int
转换进位值,负数求和会因符号位扩展导致循环无法终止,这是最常见的错误,必须主动提及并解释; - 循环条件错误:循环条件应为
b != 0
(而非a != 0
),因为我们需要等待“进位值”消失,而非“无进位结果”消失; - 混淆无进位加法和进位计算:记住“异或算无进位,与+左移算进位”,不要颠倒两者的作用;
- 忽略补码知识:面试中可能会被追问“负数如何用位运算求和”,需简要说明“计算机用补码存储负数,位运算可直接对补码操作,无需额外处理符号”。
思考题
如果题目允许使用 +
-
运算符,但禁止使用 *
/
运算符,如何计算两整数的乘积?
(提示:同样可用位运算,将乘法拆分为“左移”和“加法”,例如 a*3 = a*2 + a = (a << 1) + a
)
可以用“俄罗斯农民乘法”或“二进制拆分法”,你能写出最优版本吗?欢迎评论区贴代码,我会选最优答案置顶!
下题预告
137. 只出现一次的数字 II
下一篇将讲解力扣 137. 只出现一次的数字 II,带你跳出 “异或直接消去重复” 的思维惯性,解锁位运算 “按位统计次数、模运算筛选唯一” 的核心逻辑——当数组中除一个元素外,其余元素均出现三次时,如何用常数空间、线性时间精准定位那个唯一元素,将是我们深入探讨的重点,尤其会聚焦 “位级计数” 这一面试高频的位运算进阶技巧。
如果这篇内容对你觉得不错,别忘了 点赞👍 + 收藏⭐ + 关注👀 哦!
有问题欢迎在评论区留言,我会认真思考并及时回复!