LeetCode 66. 加一:两种高效解法详解

40 阅读6分钟

在LeetCode的基础算法题中,「加一」是一道经典的数组操作题,看似简单却藏着对边界场景的考量,尤其适合新手入门数组遍历和边界处理。这道题的核心是模拟大整数加1的过程——因为题目中的整数可能极大,无法直接转为Number类型计算,只能通过数组逐位操作来实现,今天就来详细拆解两种高效解法,帮大家吃透这道题的思路和细节。

一、题目回顾(清晰理解需求)

题目给出一个表示大整数的整数数组 digits,满足以下条件:

  • digits[i] 是整数的第 i 位数字,按「从左到右、从最高位到最低位」排列(比如 digits = [1,2,3] 表示整数 123);

  • 这个大整数不包含任何前导 0(即 digits[0] 不会是 0,除非数组长度为1且 digits = [0])。

要求:将这个大整数加 1,返回结果的数字数组(比如 [1,2,3] 加1后返回 [1,2,4],[9,9,9] 加1后返回 [1,0,0,0])。

核心难点:处理「末尾全是9」的边界情况(如 [9]→[1,0]、[9,9]→[1,0,0]),避免直接操作导致的逻辑漏洞。

二、解法一:逆序遍历找非9元素(清晰易懂,逻辑直观)

1. 思路分析

大整数加1,本质是从「最低位」(数组最后一位)开始计算,只有当当前位是9时,加1才会产生进位,否则直接加1即可,后续位无需修改(全部置0)。基于这个逻辑,我们可以分两步走:

  1. 逆序遍历数组,找到第一个不为9的元素:这个元素就是加1的位置,其后面的所有元素(都是9)需要置为0;

  2. 处理边界情况:如果遍历完所有元素都没有找到非9元素(即数组全是9),说明加1后会新增一位,在数组开头插入1即可。

2. 完整TS代码

function plusOne_1(digits: number[]): number[] {
  // 逆序遍历,找到第一个不为9的元素索引
  let index = -1;
  for (let i = digits.length - 1; i >= 0; i--) {
    if (digits[i] === 9) {
      continue; // 是9,继续向前找
    } else {
      index = i; // 找到非9元素,记录索引并退出循环
      break;
    }
  }

  // 将找到的非9元素后面的所有元素置为0(都是9,加1后需归零)
  for (let i = index + 1; i < digits.length; i++) {
    digits[i] = 0;
  }

  // 处理边界:全是9的情况(index仍为-1),否则给非9元素加1
  if (index === -1) {
    digits.unshift(1); // 新增最高位1
  } else {
    digits[index]++;
  }
  return digits;
};

3. 代码细节拆解

  • index初始值为-1:用于标记「是否找到非9元素」,若遍历结束仍为-1,说明数组全是9;

  • 第一次逆序遍历:只找第一个非9元素,找到后立即退出,避免多余遍历,时间复杂度O(n)(最坏情况遍历整个数组);

  • 第二次遍历:仅将非9元素后面的元素置0,无需遍历整个数组,进一步优化效率;

  • unshift(1):在数组开头插入1,对应「全9加1」的场景(如 [9,9] → [1,0,0])。

三、解法二:逆序遍历直接处理(更简洁,减少一次遍历)

1. 思路优化

解法一的核心逻辑是「先找非9元素,再置0、加1」,而解法二将这两个步骤合并,在逆序遍历的过程中直接处理:

  1. 逆序遍历数组,遇到非9元素时,直接加1,然后将其后面的所有元素置为0,直接返回数组(因为后续元素无需再处理);

  2. 如果遍历完所有元素都没有遇到非9元素(全9),则创建一个长度为n+1的新数组,默认填充0,将第一个元素设为1(相当于在原数组前加1),返回新数组。

这种解法的优势的是「提前返回」,减少不必要的遍历,代码更简洁,逻辑更紧凑。

2. 完整TS代码

function plusOne_2(digits: number[]): number[] {
  const n = digits.length;
  for (let i = n - 1; i >= 0; i--) {
    if (digits[i] !== 9) {
      digits[i]++; // 非9元素直接加1
      // 将后面的所有9置为0
      for (let j = i + 1; j < n; j++) {
        digits[j] = 0;
      }
      return digits; // 处理完毕,直接返回
    }
  }

  // 走到这里,说明数组全是9
  const ans = new Array(n + 1).fill(0);
  ans[0] = 1;
  return ans;
};

3. 代码细节拆解

  • 提前返回:只要找到非9元素,处理完当前位加1和后续位置0后,直接返回数组,无需继续遍历,效率更高;

  • 新数组创建:全9场景下,不修改原数组,直接创建新数组(长度n+1),避免unshift操作(unshift会改变原数组,且时间复杂度为O(n),但此处两种方式效率差异不大);

  • 边界处理更简洁:无需额外标记index,通过「遍历是否结束」判断是否全为9,逻辑更紧凑。

四、两种解法对比(选择适合自己的方式)

对比维度解法一解法二
时间复杂度O(n)(最坏两次遍历,实际效率接近O(n))O(n)(最坏一次遍历,提前返回更高效)
空间复杂度O(1)(除了原数组,无额外空间,unshift修改原数组)O(n)(全9场景下创建新数组,否则O(1))
代码复杂度稍繁琐,逻辑分步清晰,适合新手理解更简洁,逻辑紧凑,适合熟练后使用
核心优势步骤明确,边界处理直观,易调试提前返回,减少遍历,代码更优雅

五、常见测试用例(验证解法正确性)

无论是哪种解法,都需要覆盖以下几种核心测试用例,确保逻辑无漏洞:

  1. 普通情况(无进位):digits = [1,2,3] → 输出 [1,2,4];

  2. 末尾有进位(非全9):digits = [1,2,9] → 输出 [1,3,0];

  3. 全9情况(边界):digits = [9,9,9] → 输出 [1,0,0,0];

  4. 单个元素(边界):digits = [9] → 输出 [1,0],digits = [0] → 输出 [1]。

大家可以将这几个用例代入两种解法,验证代码是否能正确运行,也可以自己动手调试,感受两种思路的差异。

六、总结与思考

LeetCode 66.加一虽然是一道简单题,但核心考察的是「边界处理」和「数组遍历技巧」——很多新手容易忽略「全9加1」的场景,导致代码报错。通过这两种解法,我们可以学到:

  • 逆序遍历是处理「从低位到高位」计算的常用技巧(如加法、减法的模拟);

  • 边界场景的提前判断的重要性,能避免不必要的计算,提升代码效率;

  • 同一问题可以有不同的实现思路,既要追求代码简洁,也要保证逻辑清晰,适合自己的才是最好的。