题目描述

✅ 371. 两整数之和

image-20260929094913234

题意分析

在实现中不使用加法和减法运算符,求两个整数的和。输入可能为负数,不能只处理无符号数或另按符号分类相加。

可以直接模拟二进制加法:先同时算出各位不考虑进位的结果,再把产生的进位继续合并。题目输入范围内的最终结果能够由普通整数保存。

解法:位运算模拟加法

核心思路

[!blue]

先只看同一位的两位相加:恰有一个 1 时,本位为 1 且不进位;两个都是 1 时,本位变为 0 并向高一位进位;两个都是 0 时均为零。因此 a ^ b 同时给出所有位的无进位结果,a & b 找出会产生进位的位置,再左移一位将进位送到应加入的高位。

将 a 视为当前无进位结果,b 视为尚待合并的另一部分。每轮先从同一份旧值算出 sum = a ^ b 和 carry = (a & b) << 1,再分别赋给 a、b。新产生的进位还可能与高位已有的 1 冲突,所以需要重复,不能只做一轮。

在固定 $w$ 位补码中,新旧两部分的总和始终模 $2^w$ 同余。中间值按有符号数解释时可能越过通常的数值范围,但保留下来的位模式仍然代表同一个加法结果。负数使用相同补码规则,因此无需单独的符号分支。

只要新进位非零,它最低的置位就比旧 b 的最低置位至少高一位:按位与不会制造更低的 1,随后左移又前进一位。固定宽度下最多传播 $w$ 轮,进位就会消失。此时 b == 0,全部结果已经合并在 a 中,返回它即可。

解题步骤

  1. 只要 b != 0,先计算旧 a、b 的异或值并保存。
  2. 再由旧值计算按位与左移后的进位,确保没有提前覆盖任何操作数。
  3. 用这两个临时值更新 a、b,继续处理进位。
  4. 当 b == 0 时返回 a;若初始 b 就是零,无需进入循环。

代码实现

class Solution {
    public int getSum(int a, int b) {
        while (b != 0) {
            // 两个新值都由旧的两数计算,不能提前覆盖。
            int sum = a ^ b;
            // 重合的一位产生进位,移到更高一位继续合并。
            int carry = (a & b) << 1;

            a = sum;
            b = carry;
        }

        return a;
    }
}
func getSum(a int, b int) int {
    for b != 0 {
        // 两个新值都由旧的两数计算,不能提前覆盖。
        sum := a ^ b
        // 重合的一位产生进位,移到更高一位继续合并。
        carry := (a & b) << 1
        a = sum
        b = carry
    }
    return a
}

复杂度分析

  • 时间复杂度:$O(w)$,w 为当前整数位宽,固定位宽下为常数时间。
  • 空间复杂度:$O(1)$。

关键点总结

[!green]

  • 两个新值都必须来自旧状态。
  • 进位属于下一位,需要左移。
  • Java 与 Go 的整数位宽可能不同,按进位是否为零结束。

易错点总结

[!yellow]

  • 先更新 a 再计算 carry:丢失旧的重合位。
  • 进位不左移:产生的进位仍停在原位,没有按二进制位权加入高一位。
  • 用 a 是否为零控制循环:零与非零数相加时可能提前结束。
  • 固定只做少量轮次:长进位链可能尚未处理完。

相似题目

题目 难度 关联与区别
67. 二进制求和 简单 同样把每位的和与进位分开,本题通过异或和按位与直接并行处理机器字。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2025/71950734
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!