算法题 两整数之和

LeetCode 371. 两整数之和

问题描述

给定两个整数 ab不使用 +- 运算符,计算并返回它们的和。

示例

输入: a = 1, b = 2
输出: 3

输入: a = 2, b = 3
输出: 5

约束条件

  • -1000 <= a, b <= 1000

算法思路

核心思想:位运算模拟加法

在二进制加法中,每一位的计算包含两个部分:

  1. 不考虑进位的加法:使用 异或运算(XOR) 实现

    • 0 + 0 = 00 ^ 0 = 0
    • 0 + 1 = 10 ^ 1 = 1
    • 1 + 0 = 11 ^ 0 = 1
    • 1 + 1 = 0(不考虑进位)→ 1 ^ 1 = 0
  2. 计算进位:使用 与运算(AND) 并左移一位

    • 只有当两个位都为 1 时才会产生进位
    • 1 + 1 = 10(二进制),进位为 1,需要左移一位到高位

步骤

  1. 计算不考虑进位的和:sum = a ^ b
  2. 计算进位:carry = (a & b) << 1
  3. 将进位加到和上:递归或迭代处理 sumcarry
  4. 当进位为 0 时,返回最终结果

处理负数

在 Java 中,整数使用 32 位补码 表示,负数的最高位为 1。 Java 没有无符号整数类型:

  1. 进位溢出:当进位左移到第 32 位时,会变成负数
  2. 循环终止:需要确保进位最终会变为 0
  3. 位运算处理:使用位掩码 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

迭代:

  1. 初始状态a = 01, b = 10
  2. 第1轮
    • sum = 01 ^ 10 = 11(3)
    • carry = (01 & 10) << 1 = 00 << 1 = 00(0)
    • b = 0,循环结束
  3. 返回结果:3

a = 5, b = 7

  1. 初始状态a = 101, b = 111
  2. 第1轮
    • sum = 101 ^ 111 = 010(2)
    • carry = (101 & 111) << 1 = 101 << 1 = 1010(10)
  3. 第2轮
    • sum = 010 ^ 1010 = 1000(8)
    • carry = (010 & 1010) << 1 = 0010 << 1 = 0100(4)
  4. 第3轮
    • sum = 1000 ^ 0100 = 1100(12)
    • carry = (1000 & 0100) << 1 = 0000 << 1 = 0000(0)
  5. 返回结果: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
}

关键点

  1. 位运算

    • 异或(XOR):实现不考虑进位的加法
    • 与(AND)+ 左移:计算进位位置
  2. 负数处理

    • Java 使用 32 位补码表示整数
    • 使用位掩码 0xFFFFFFFFL 限制在 32 位范围内
    • 结果转换时处理负数
  3. 溢出

    • 使用 long 类型进行中间计算
    • 最终结果转换回 int 类型
    • 当结果超过 Integer.MAX_VALUE 时,需要减去 0x100000000L
  4. 终止

    • 进位最终会变为 0,每次进位都会向高位移动
    • 最多 32 次迭代后终止

常见问题

  1. 如何处理整数溢出?

    • Java 中整数溢出会自动截断到32位
    • 使用 long 类型和位掩码确保计算正确
  2. 为什么需要位掩码 0xFFFFFFFFL?

    • 确保只保留低32位,模拟32位整数运算
    • 处理负数的补码表示
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值