题目
不使用 + 和 -,计算两整数之和。
输入: a = 1, b = 2 → 输出: 3
输入: a = 2, b = 3 → 输出: 5
代码
class Solution {
public int getSum(int a, int b) {
while (b != 0) {
int carry = (a & b) << 1; // 进位
a = a ^ b; // 无进位相加
b = carry;
}
return a;
}
}
核心思想:把加法拆成两半
十进制加法 15 + 27 你会拆成"个位相加"和"进位"两步。二进制也一样,而且拆分方式更干净:
| 运算 | 作用 | 类比十进制 |
|---|---|---|
a ^ b |
无进位相加(本位结果) | 5+7 的个位是 2(不带进位) |
(a & b) << 1 |
进位(哪几位要往前进) | 5+7 满十进一 |
为什么是这两个式子?
^是"不同为 1":1^1=0(1+1 本位写 0)、1^0=1、0^0=0------ 恰好就是不考虑进位时的加法结果&是"同为 1 才 1":只有1&1才产生进位,0不会。但进位要加到高一位 ,所以<< 1
然后重复这个过程:把"无进位和"和"进位"再加一遍,直到进位为 0。
逐步演示 a = 5, b = 3(5+3=8)
第1轮: a=0101 b=0011
a^b = 0110 (6) ← 无进位和
(a&b)<<1= 0010 (2) ← 进位(第0位都是1,进到第1位)
第2轮: a=0110 b=0010
a^b = 0100 (4)
(a&b)<<1= 0100 (4) ← 第1位都是1,进到第2位
第3轮: a=0100 b=0100
a^b = 0000 (0) ← 全撞上了,本位全写0
(a&b)<<1= 1000 (8) ← 进位到第3位
第4轮: a=0000 b=1000
a^b = 1000 (8) ← 没有重叠了
(a&b)<<1= 0000 (0) ← 进位归零!
b = 0,循环结束 → 结果 8 ✓
关键:每一轮进位都往左移一位,所以低位会不断被 0 填满,最终进位被"挤"出 int 的 32 位范围,变成 0,循环必然终止。
实测覆盖(含负数)
| a | b | 期望 | 位运算加法 |
|---|---|---|---|
| 1 | 2 | 3 | ✓ |
| 2 | 3 | 5 | ✓ |
| -1 | 1 | 0 | ✓ |
| 5 | 7 | 12 | ✓ |
| -3 | -5 | -8 | ✓ |
| 0 | 0 | 0 | ✓ |
| -2 | 3 | 1 | ✓ |
| 20 | 30 | 50 | ✓ |
负数也完全没问题------因为 Java 用补码存储,加减法的二进制规则对有符号数同样成立,位运算不关心你怎么解释符号位。
两个值得记住的细节
① 循环为什么一定会停
每轮 b 都左移一位,最多 32 轮后所有进位都被挤出 int 范围,b 必然变成 0。所以最多循环 32 次,时间复杂度其实是 O(1)。
② 关于 >>> 的常见说法
很多题解会强调"必须用无符号右移 >>>"。但你这版代码用的是左移进位 + while (b != 0),实测负数用例全部正常 ------因为退出条件是 b == 0,不依赖右移来收敛。
需要注意 >>> 的是另一种写法(Python 模拟 32 位整数时常用右移来取模)。如果你的代码已经通过,说明这版写法在 Java 里是自洽的,不用改。
一句话总结
a ^ b算出"不管进位的和",(a & b) << 1算出"该进的位",把这俩再相加,反复到进位消失为止------这就是加法器的工作原理。
思维串联:这题把你笔记里的东西全串起来了------
^异或:不同为 1 → 无进位加法&与:同为 1 才 1 → 找进位位置<<左移:造掩码 / 把进位挪到高一位
延伸 :这题对应计算机组成原理里的半加器 (^ 是求和、& 是进位)和全加器 (多输入一个低位进位)。减法 a - b 同理可以写成 getSum(a, getSum(~b, 1))(即 a + (-b),而 -b = ~b + 1)。