力扣解题-66. 加一

3 阅读6分钟

力扣解题-66. 加一

给定一个表示 大整数 的整数数组 digits,其中 digits[i] 是整数的第 i 位数字。这些数字按从左到右,从最高位到最低位排列。这个大整数不包含任何前导 0。

将大整数加 1,并返回结果的数字数组。

示例 1:

输入:digits = [1,2,3]

输出:[1,2,4]

解释:输入数组表示数字 123。加 1 后得到 123 + 1 = 124,结果为 [1,2,4]。

示例 2:

输入:digits = [4,3,2,1]

输出:[4,3,2,2]

解释:输入数组表示数字 4321。加 1 后得到 4321 + 1 = 4322,结果为 [4,3,2,2]。

示例 3:

输入:digits = [9]

输出:[1,0]

解释:输入数组表示数字 9。加 1 得到 9 + 1 = 10,结果为 [1,0]。

提示:

1 <= digits.length <= 100

0 <= digits[i] <= 9

digits 不包含任何前导 0。

Related Topics

数组、数学


第一次解答

解题思路

核心方法:逆序遍历进位法(最优原地解法),从数组末尾(最低位)开始遍历,处理加1后的进位逻辑,仅在所有位都是9时才新建数组扩容,时间复杂度O(n)、空间复杂度最优(最好O(1),最坏O(n)),是本题的最优解法。

核心逻辑拆解

大整数加1的核心是“处理进位”,关键在于区分“普通位加1”和“全9进位扩容”两种场景:

  1. 逆序遍历(从最低位到最高位):从数组最后一位开始遍历,模拟手工加法的“从右往左算”;
  2. 普通位处理
    • 若当前位数字≠9,直接将该位+1,无进位,可立即返回原数组(后续高位无需处理);
    • 若当前位数字=9,加1后变为0,产生进位,继续遍历前一位;
  3. 全9场景处理
    • 若遍历完所有位仍未返回(说明所有位都是9),需新建长度+1的数组;
    • 新数组首位设为1,其余位默认0(如[9,9]→[1,0,0])。
具体步骤(分场景说明)
场景1:无进位(示例1 digits=[1,2,3])
  • 遍历i=2(数字3):3≠9 → 3+1=4 → 数组变为[1,2,4] → 直接返回;
场景2:部分进位(示例2 digits=[4,3,2,1])
  • 遍历i=3(数字1):1≠9 → 1+1=2 → 数组变为[4,3,2,2] → 直接返回;
场景3:全进位(示例3 digits=[9])
  • 遍历i=0(数字9):9=9 → 置为0 → 遍历结束;
  • 新建数组res=[1,0] → 返回。
执行流程可视化(以digits=[9,9,9]为例)
遍历索引i当前值操作数组状态后续动作
29置为0[9,9,0]继续遍历i=1
19置为0[9,0,0]继续遍历i=0
09置为0[0,0,0]遍历结束,扩容
--新建数组[1,0,0,0][1,0,0,0]返回新数组
性能说明
  • 时间复杂度:O(n)
    • 最好情况(无进位):仅遍历1位,O(1);
    • 最坏情况(全9):遍历所有n位,O(n);
    • 平均复杂度:O(n)。
  • 空间复杂度
    • 最好情况(无进位/部分进位):O(1)(原地修改数组);
    • 最坏情况(全9):O(n)(新建长度n+1的数组);
    • 整体为原地算法的最优空间复杂度。
  • 优势
    1. 短路逻辑:找到第一个非9位加1后立即返回,无需遍历全部元素;
    2. 原地修改:避免不必要的数组拷贝,效率最优;
    3. 逻辑简洁:仅需一次逆序遍历,无冗余操作。
    public int[] plusOne(int[] digits) {
        for (int i = digits.length - 1; i >= 0; i--) {
            if (digits[i] != 9) {
                digits[i]++;
                return digits;
            }
            digits[i] = 0;
        }
        int[] res = new int[digits.length + 1];
        res[0] = 1;
        return res;
    }

示例解答

解题思路

解法1:通用进位法(兼容任意加数,拓展性强)

核心方法:将“加1”抽象为“处理进位”的通用逻辑,引入carry进位变量,可轻松拓展为加任意数(如加5、加10),逻辑更通用,适合理解进位的核心原理。

代码实现
public int[] plusOne(int[] digits) {
    int carry = 1; // 初始进位为1(对应加1)
    // 逆序遍历处理进位
    for (int i = digits.length - 1; i >= 0 && carry > 0; i--) {
        int sum = digits[i] + carry;
        digits[i] = sum % 10; // 当前位值
        carry = sum / 10;     // 新的进位
    }
    // 仍有进位(全9场景)
    if (carry > 0) {
        int[] res = new int[digits.length + 1];
        res[0] = carry;
        // 复制原数组到新数组(Java中int数组默认值为0,无需手动赋值)
        System.arraycopy(digits, 0, res, 1, digits.length);
        return res;
    }
    return digits;
}
核心逻辑说明
  1. 进位初始化carry=1(表示加1的初始进位);
  2. 逆序处理
    • 计算当前位总和sum = digits[i] + carry
    • 当前位值为sum%10(如9+1=10 → 0);
    • 新进位为sum/10(如9+1=10 → 1);
    • 若进位为0,提前终止遍历(短路优化);
  3. 扩容处理:若遍历结束仍有进位,新建数组并将进位置于首位。
优势说明
  • 拓展性强:修改carry初始值即可实现“加n”(如加5则carry=5);
  • 逻辑通用:符合“大数加法”的通用范式,便于解决同类问题(如两数相加);
  • 可读性高:明确的进位变量,新手易理解加法的核心逻辑。
解法2:字符串转换法(直观但效率略低)

核心方法:将数组转换为字符串→转成大数→加1→再转回数组,逻辑最直观但涉及字符串/数字转换,效率略低,适合理解问题本质但不推荐工程使用。

代码实现
import java.math.BigInteger;

public int[] plusOne(int[] digits) {
    // 1. 数组转字符串
    StringBuilder sb = new StringBuilder();
    for (int d : digits) {
        sb.append(d);
    }
    // 2. 字符串转大数加1
    BigInteger num = new BigInteger(sb.toString());
    num = num.add(BigInteger.ONE);
    // 3. 结果转回数组
    String resStr = num.toString();
    int[] res = new int[resStr.length()];
    for (int i = 0; i < resStr.length(); i++) {
        res[i] = resStr.charAt(i) - '0';
    }
    return res;
}
核心逻辑说明
  1. 数组转字符串:拼接所有数字字符,如[1,2,3]→"123";
  2. 大数加1:使用BigInteger避免整数溢出(虽然本题digits长度≤100,但通用场景需考虑);
  3. 字符串转数组:将加1后的字符串逐字符转回数字数组。
性能说明
  • 时间复杂度:O(n)(转换过程需遍历数组/字符串);
  • 空间复杂度:O(n)(存储字符串和新数组);
  • 劣势:
    1. 涉及多次类型转换,效率低于原地算法;
    2. 依赖BigInteger类,代码量更大;
  • 适用场景:新手理解问题本质,或需要快速实现无需优化的场景。

总结

  1. 逆序遍历进位法(第一次解答):O(n)时间+最优空间,短路逻辑+原地修改,是本题的工程最优解法;
  2. 通用进位法:O(n)时间+最优空间,拓展性强,适合理解大数加法的通用范式;
  3. 字符串转换法:O(n)时间+O(n)空间,逻辑直观但效率低,仅适合新手理解;
  4. 关键技巧
    • 核心思想:大数加1的本质是“从低位到高位处理进位”,非9位加1即终止,全9位需扩容;
    • 效率优化:逆序遍历+短路返回,避免不必要的遍历;
    • 边界处理:全9场景是唯一需要扩容的情况,需单独处理。