问题描述
给定一个表示 大整数 的整数数组 digits,其中 digits[i] 是整数的第 i 位数字。这些数字按从左到右,从最高位到最低位排列。这个大整数不包含任何前导 0。
将大整数加 1,并返回结果的数字数组。
输入: digits = [1,2,3]
输出: [1,2,4]
解释: 输入数组表示数字 123, 加 1 后得到 123 + 1 = 124。
因此,结果应该是 [1,2,4]。
输入: digits = [4,3,2,1]
输出: [4,3,2,2]
解释: 输入数组表示数字 4321, 加 1 后得到 4321 + 1 = 4322。
因此,结果应该是 [4,3,2,2]。
解法一:基础模拟 - 直观的进位处理
这个解法是最直观也是最常见的解法,它的核心思想是模拟人工加法过程:即从最低位(数组末尾)开始加 1,处理进位,逐位计算。
function plusOne(digits) {
const n = digits.length;
let carry = 1; // 初始加1相当于最低位有一个进位1
for (let i = n - 1; i >= 0; i--) {
let sum = digits[i] + carry;
digits[i] = sum % 10; // 当前位结果
carry = Math.floor(sum / 10); // 计算新的进位
}
// 处理最高位进位导致数组需要扩容的情况 (如 999 + 1 = 1000)
if (carry > 0) {
digits.unshift(carry); // 在数组头部添加进位值
}
return digits;
}
解题思想:
- 逆向遍历: 加法从最低位开始计算。
- 进位传递: 使用
carry变量记录并传递进位值。 - 处理扩容: 循环结束后检查最高位是否仍有进位,若有则在数组头部插入新元素
1。
注意点:
- 原地修改问题: 此解法直接修改了输入数组
digits,在要求不修改原数组的场景会出错(但是力扣本题允许),因此,安全做法应该是创建新数组或深拷贝。 - 忘记处理最高位进位: 遗漏最后的
if (carry > 0)检查,导致类似[9, 9]的输入无法正确输出[1, 0, 0]。 - 进位计算错误:
carry = Math.floor(sum / 10)是关键,错误写成carry = sum / 10会得到小数导致后续计算错误。
解法二:优化 - 提前终止循环
观察可以发现:一旦某一位不再产生进位,其左侧更高位的数字就不会再改变,那么,利用此特性,我们可以提前结束循环,以提升效率。
function plusOne(digits) {
const n = digits.length;
for (let i = n - 1; i >= 0; i--) {
if (digits[i] < 9) {
digits[i]++; // 当前位小于9,加1后不会进位,直接返回
return digits;
}
digits[i] = 0; // 当前位是9,加1后变成0,进位1传递给更高位
}
// 循环执行完说明所有位都是9,全部变成了0,需要在最前面补1
return [1, ...digits]; // 或 digits.unshift(1); return digits;
}
解题思想:
- 逆向寻找非 9 位: 从右向左遍历,找到第一个小于 9 的数字。
- 立即加一并返回: 将该位加 1,其右边所有位已设为 0 (由进位导致),此时结果已确定。
- 处理全 9 情况: 若遍历完所有位(说明原数全由 9 组成),则返回由
1和n个0组成的新数组。
解法优点:
- 效率提升: 大多数情况下(非全 9 的数)无需遍历整个数组。
- 逻辑清晰: 代码更简洁,更直接体现了“加一”操作的短路特性。
注意点:
- 边界条件 - 全 9: 必须正确处理所有位都是 9 的情况,返回
[1, 0, 0, ..., 0]。 - 修改原数组: 同样要注意是否允许修改原数组
digits,解法中digits[i]++和digits[i] = 0修改了原数组,如果不允许,需先复制:let result = [...digits]; // 创建副本 ... // 在result上操作
解法三:函数式编程风格 (JavaScript)
利用数组的 map 和 findIndex 方法,结合扩展运算符,可写出更简洁的函数式风格代码(主要展示思路,效率非最高):
function plusOne(digits) {
// 1. 尝试在最后一位直接加1
const tryIncrement = [...digits];
tryIncrement[digits.length - 1] += 1;
// 2. 检查是否有任何位变成了10
const hasOverflow = tryIncrement.some(digit => digit === 10);
if (!hasOverflow) return tryIncrement; // 没有进位,直接返回
// 3. 处理进位:从右向左找到第一个非9的位置
const result = [...digits];
let idx = result.length - 1;
// 4. 逆向查找第一个可以安全加1的位置 (非9的位置)
while (idx >= 0 && result[idx] === 9) {
result[idx] = 0; // 将连续的9变为0
idx--;
}
// 5. 处理找到的位置或全9的情况
if (idx >= 0) {
result[idx]++;
} else {
result.unshift(1); // 所有位都是9,在头部添加1
}
return result;
}
思想与易错点: 核心逻辑与 解法二 一致(寻找非 9 位、处理全 9),但是它的实现方式更函数式,而且也要注意: tryIncrement 只是试探,hasOverflow 判断仍需遍历数组,其效率其实不如 解法二 的直接循环。
核心解题思想总结
- 模拟人工计算: 加法的本质是从最低位开始逐位计算并处理进位。
- 逆向思维: 处理数字位操作(尤其是进位)时,从数组末尾(最低位)向开头(最高位)遍历通常更自然高效。
- 短路优化: 一旦进位停止传播,更高位无需再计算(解法 2 的核心优化点)。
- 边界处理: 特别注意所有位都是 9 的特殊情况,此时结果位数增加。