力扣解题-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进位扩容”两种场景:
- 逆序遍历(从最低位到最高位):从数组最后一位开始遍历,模拟手工加法的“从右往左算”;
- 普通位处理:
- 若当前位数字≠9,直接将该位+1,无进位,可立即返回原数组(后续高位无需处理);
- 若当前位数字=9,加1后变为0,产生进位,继续遍历前一位;
- 全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 | 当前值 | 操作 | 数组状态 | 后续动作 |
|---|---|---|---|---|
| 2 | 9 | 置为0 | [9,9,0] | 继续遍历i=1 |
| 1 | 9 | 置为0 | [9,0,0] | 继续遍历i=0 |
| 0 | 9 | 置为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的数组);
- 整体为原地算法的最优空间复杂度。
- 优势:
- 短路逻辑:找到第一个非9位加1后立即返回,无需遍历全部元素;
- 原地修改:避免不必要的数组拷贝,效率最优;
- 逻辑简洁:仅需一次逆序遍历,无冗余操作。
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;
}
核心逻辑说明
- 进位初始化:
carry=1(表示加1的初始进位); - 逆序处理:
- 计算当前位总和
sum = digits[i] + carry; - 当前位值为
sum%10(如9+1=10 → 0); - 新进位为
sum/10(如9+1=10 → 1); - 若进位为0,提前终止遍历(短路优化);
- 计算当前位总和
- 扩容处理:若遍历结束仍有进位,新建数组并将进位置于首位。
优势说明
- 拓展性强:修改
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,2,3]→"123";
- 大数加1:使用
BigInteger避免整数溢出(虽然本题digits长度≤100,但通用场景需考虑); - 字符串转数组:将加1后的字符串逐字符转回数字数组。
性能说明
- 时间复杂度:O(n)(转换过程需遍历数组/字符串);
- 空间复杂度:O(n)(存储字符串和新数组);
- 劣势:
- 涉及多次类型转换,效率低于原地算法;
- 依赖
BigInteger类,代码量更大;
- 适用场景:新手理解问题本质,或需要快速实现无需优化的场景。
总结
- 逆序遍历进位法(第一次解答):O(n)时间+最优空间,短路逻辑+原地修改,是本题的工程最优解法;
- 通用进位法:O(n)时间+最优空间,拓展性强,适合理解大数加法的通用范式;
- 字符串转换法:O(n)时间+O(n)空间,逻辑直观但效率低,仅适合新手理解;
- 关键技巧:
- 核心思想:大数加1的本质是“从低位到高位处理进位”,非9位加1即终止,全9位需扩容;
- 效率优化:逆序遍历+短路返回,避免不必要的遍历;
- 边界处理:全9场景是唯一需要扩容的情况,需单独处理。