LeetCode 371. 两整数之和
问题描述
给定两个整数 a 和 b,不使用 + 和 - 运算符,计算并返回它们的和。
示例:
输入: a = 1, b = 2
输出: 3
输入: a = 2, b = 3
输出: 5
约束条件:
-1000 <= a, b <= 1000
算法思路
核心思想:位运算模拟加法
在二进制加法中,每一位的计算包含两个部分:
-
不考虑进位的加法:使用 异或运算(XOR) 实现
0 + 0 = 0→0 ^ 0 = 00 + 1 = 1→0 ^ 1 = 11 + 0 = 1→1 ^ 0 = 11 + 1 = 0(不考虑进位)→1 ^ 1 = 0
-
计算进位:使用 与运算(AND) 并左移一位
- 只有当两个位都为 1 时才会产生进位
1 + 1 = 10(二进制),进位为 1,需要左移一位到高位
步骤:
- 计算不考虑进位的和:
sum = a ^ b - 计算进位:
carry = (a & b) << 1 - 将进位加到和上:递归或迭代处理
sum和carry - 当进位为 0 时,返回最终结果
处理负数
在 Java 中,整数使用 32 位补码 表示,负数的最高位为 1。 Java 没有无符号整数类型:
- 进位溢出:当进位左移到第 32 位时,会变成负数
- 循环终止:需要确保进位最终会变为 0
- 位运算处理:使用位掩码
0xFFFFFFFFL来处理 32 位整数
代码实现
方法一:迭代
class Solution {
/**
* 不使用+和-运算符计算两整数之和(迭代)
*
* @param a 第一个整数
* @param b 第二个整数
* @return 两数之和
*/
public int getSum(int a, int b) {
// 使用long类型避免溢出问题,限制在32位范围内
long x = a;
long y = b;
// 当进位不为0时继续循环
while (y != 0) {
// 计算不考虑进位的和(异或运算)
long sum = x ^ y;
// 计算进位:与运算后左移一位
long carry = (x & y) << 1;
// 为下一轮迭代准备
x = sum;
y = carry;
// 限制在32位整数范围内(处理负数)
x = x & 0xFFFFFFFFL;
y = y & 0xFFFFFFFFL;
}
// 处理结果:如果结果是负数,需要正确转换回int
return (int)(x > Integer.MAX_VALUE ? x - 0x100000000L : x);
}
}
方法二:递归
class Solution {
/**
* 不使用+和-运算符计算两整数之和(递归)
*
* @param a 第一个整数
* @param b 第二个整数
* @return 两数之和
*/
public int getSum(int a, int b) {
// 递归终止条件:当b为0时,a就是最终结果
if (b == 0) {
return a;
}
// 计算不考虑进位的和
int sum = a ^ b;
// 计算进位(需要处理溢出)
int carry = (a & b) << 1;
// 递归处理sum和carry
return getSum(sum, carry);
}
}
方法三:优化递归(处理溢出)
class Solution {
/**
* 优化递归,正确处理32位整数溢出
*
* @param a 第一个整数
* @param b 第二个整数
* @return 两数之和
*/
public int getSum(int a, int b) {
// 使用long类型确保计算正确
return helper((long)a, (long)b);
}
private int helper(long a, long b) {
if (b == 0) {
// 处理32位整数范围
return (int)(a & 0xFFFFFFFFL);
}
long sum = a ^ b;
long carry = (a & b) << 1;
// 限制在32位范围内
return helper(sum & 0xFFFFFFFFL, carry & 0xFFFFFFFFL);
}
}
算法分析
-
时间复杂度:O(1)
- 最多需要 32 次迭代(32 位整数)
-
空间复杂度:
- 迭代法:O(1)
- 递归法:O(1)(最多 32 层递归栈)
-
方法对比:
- 迭代:安全,避免栈溢出
- 递归:代码简洁,有栈溢出风险
算法过程
a = 1, b = 2 :
迭代:
- 初始状态:
a = 01, b = 10 - 第1轮:
sum = 01 ^ 10 = 11(3)carry = (01 & 10) << 1 = 00 << 1 = 00(0)b = 0,循环结束
- 返回结果:3
a = 5, b = 7 :
- 初始状态:
a = 101, b = 111 - 第1轮:
sum = 101 ^ 111 = 010(2)carry = (101 & 111) << 1 = 101 << 1 = 1010(10)
- 第2轮:
sum = 010 ^ 1010 = 1000(8)carry = (010 & 1010) << 1 = 0010 << 1 = 0100(4)
- 第3轮:
sum = 1000 ^ 0100 = 1100(12)carry = (1000 & 0100) << 1 = 0000 << 1 = 0000(0)
- 返回结果:12
测试用例
public static void main(String[] args) {
Solution solution = new Solution();
// 测试用例1:标准正数相加
System.out.println("Test 1 (1+2): " + solution.getSum(1, 2)); // 3
// 测试用例2:包含0的情况
System.out.println("Test 2 (0+5): " + solution.getSum(0, 5)); // 5
System.out.println("Test 3 (5+0): " + solution.getSum(5, 0)); // 5
// 测试用例3:负数相加
System.out.println("Test 4 (-1+1): " + solution.getSum(-1, 1)); // 0
System.out.println("Test 5 (-2+-3): " + solution.getSum(-2, -3)); // -5
// 测试用例4:正负数相加
System.out.println("Test 6 (5+-3): " + solution.getSum(5, -3)); // 2
System.out.println("Test 7 (-5+3): " + solution.getSum(-5, 3)); // -2
// 测试用例5:边界情况
System.out.println("Test 8 (1000+1000): " + solution.getSum(1000, 1000)); // 2000
System.out.println("Test 9 (-1000+-1000): " + solution.getSum(-1000, -1000)); // -2000
// 测试用例6:大数相加
System.out.println("Test 10 (2147483647+1): " + solution.getSum(2147483647, 1)); // -2147483648 (溢出)
// 测试用例7:相同数字
System.out.println("Test 11 (5+5): " + solution.getSum(5, 5)); // 10
// 测试用例8:复杂进位
System.out.println("Test 12 (7+9): " + solution.getSum(7, 9)); // 16
}
关键点
-
位运算:
- 异或(XOR):实现不考虑进位的加法
- 与(AND)+ 左移:计算进位位置
-
负数处理:
- Java 使用 32 位补码表示整数
- 使用位掩码
0xFFFFFFFFL限制在 32 位范围内 - 结果转换时处理负数
-
溢出:
- 使用
long类型进行中间计算 - 最终结果转换回
int类型 - 当结果超过
Integer.MAX_VALUE时,需要减去0x100000000L
- 使用
-
终止:
- 进位最终会变为 0,每次进位都会向高位移动
- 最多 32 次迭代后终止
常见问题
-
如何处理整数溢出?
- Java 中整数溢出会自动截断到32位
- 使用
long类型和位掩码确保计算正确
-
为什么需要位掩码 0xFFFFFFFFL?
- 确保只保留低32位,模拟32位整数运算
- 处理负数的补码表示

被折叠的 条评论
为什么被折叠?



