bigywater.tech
算法与数据结构 · LeetCode 66

LeetCode 66. 加一

给定一个用数字数组表示的非负整数(最高位在最前面,没有前导 0,[1, 2, 3] 表示 123),返回加 1 之后的数字数组。做法很直接:从最低位开始,遇到不是 9 的位就加 1 并结束,遇到 9 就变成 0 并继续向前进位;如果所有位都是 9,就在最前面补一个 1。这道题的重点是不要把数组转成语言内置的整数:数字可以有 100 位。本文用真实运行展示只给最后一位加 1 的错误,以及为什么直接转成 JavaScript 的数字会丢失精度,给出五种语言的实现,并把进位过程做成分步演示。

20,000
组随机对拍,与暴力解的结果全部一致
4 步
分步演示,每一帧都来自解法的真实执行,另附 5 种语言实现

题意与示例

完整题目见官方页面(链接在文末)。数组里的数字是 0 到 9,首位不为 0(数字 0 本身除外)。基准解把数组拼成 Python 的大整数,加 1,再拆回数字,这在 Python 里没有溢出问题,是与逐位进位不同的写法。一共 12 组用例,覆盖:官方示例、单个 9、单个 0、全是 9、只有末位是 9、20 位的大数:

真实实测 · 本机执行输出
输入 [[1,2,3]]
输出 [1,2,4]
输入 [[4,3,2,1]]
输出 [4,3,2,2]
输入 [[9]]
输出 [1,0]
输入 [[0]]
输出 [1]
输入 [[9,9,9]]
输出 [1,0,0,0]
输入 [[1,9,9]]
输出 [2,0,0]

陷阱:忘了进位,或者转成内置整数

第一个错误是只给最后一位加 1:末位是 9 时得到 10,一个数组元素变成了两位数。第二个错误是偷懒:把整个数组拼成数字,加 1,再拆开。这在数字很长时会出问题:语言里的整数或浮点数装不下。下面是这两种做法的真实结果,第二部分用的是 JavaScript(它的 Number 是双精度浮点数,只能精确表示到 2^53 也就是约 9 × 10^15):

真实实测 · 本机执行输出
# 只给最后一位加 1、不处理进位的写法
[1, 2, 3]: 只加最后一位 -> [1, 2, 4]    solve -> [1, 2, 4]
[9]: 只加最后一位 -> [10]    solve -> [1, 0]
[1, 9, 9]: 只加最后一位 -> [1, 9, 10]    solve -> [2, 0, 0]
[9, 9, 9]: 只加最后一位 -> [9, 9, 10]    solve -> [1, 0, 0, 0]

把整个数组转成语言内置的数字再加 1,为什么不行(20 位数字,JavaScript 的 Number 是双精度浮点):
原数字        : 12345678901234567890
Number 加 1   : 12345678901234567000 (Number.MAX_SAFE_INTEGER = 9007199254740991)
BigInt 加 1   : 12345678901234567891

只给最后一位加 1 时,[9] 得到 [10],[1, 9, 9] 得到 [1, 9, 10],都不是合法的数字数组;[1, 2, 3] 碰巧对了,因为末位不是 9。20 位数字 12345678901234567890 用 Number 加 1,得到 12345678901234567000,后面的几位数字丢了;用 BigInt 才能得到正确的 12345678901234567891。所以这道题必须逐位处理。

逐位进位:五种语言的实现

从最后一位往前扫描:如果这一位小于 9,加 1 后直接返回,因为不再产生进位;否则把它设为 0,继续处理前一位。循环结束还没返回,说明所有位都是 9,结果是 1 后面跟着全 0:新建一个长度加一的数组(Java)或者在最前面插入 1(其他语言)。Python、Java、Go、JavaScript、C++ 都是真实编译运行的。

Python · 解法代码def solve(digits): for i in range(len(digits) - 1, -1, -1): # 从最低位往高位 if digits[i] < 9: digits[i] += 1 # 这一位不是 9:加 1 就结束,不需要进位 return digits digits[i] = 0 # 是 9:变成 0,继续向前进位 return [1] + digits # 每一位都是 9(如 999):最前面补一个 1
真实实测 · Python · 前几组用例的输出
$ python3 solution.py   # 统一入口:逐行读取 cases.jsonl,调用上面的 solve,打印 JSON
[[1,2,3]]  →  [1,2,4]
[[4,3,2,1]]  →  [4,3,2,2]
[[9]]  →  [1,0]
[[0]]  →  [1]
[[9,9,9]]  →  [1,0,0,0]
[[1,9,9]]  →  [2,0,0]
Java · 解法代码class Solution { public int[] plusOne(int[] digits) { for (int i = digits.length - 1; i >= 0; i--) { // 从最低位往高位 if (digits[i] < 9) { digits[i]++; // 这一位不是 9:加 1 就结束,不需要进位 return digits; } digits[i] = 0; // 是 9:变成 0,继续向前进位 } int[] res = new int[digits.length + 1]; // 每一位都是 9(如 999):最前面补一个 1 res[0] = 1; return res; } }
真实实测 · Java · 前几组用例的输出
$ javac -d build Main.java Solution.java && java -cp build Main < cases.jsonl
[[1,2,3]]  →  [1,2,4]
[[4,3,2,1]]  →  [4,3,2,2]
[[9]]  →  [1,0]
[[0]]  →  [1]
[[9,9,9]]  →  [1,0,0,0]
[[1,9,9]]  →  [2,0,0]
Go · 解法代码func plusOne(digits []int) []int { for i := len(digits) - 1; i >= 0; i-- { // 从最低位往高位 if digits[i] < 9 { digits[i]++ // 这一位不是 9:加 1 就结束,不需要进位 return digits } digits[i] = 0 // 是 9:变成 0,继续向前进位 } return append([]int{1}, digits...) // 每一位都是 9(如 999):最前面补一个 1 }
真实实测 · Go · 前几组用例的输出
$ go build -o bin . && ./bin < cases.jsonl
[[1,2,3]]  →  [1,2,4]
[[4,3,2,1]]  →  [4,3,2,2]
[[9]]  →  [1,0]
[[0]]  →  [1]
[[9,9,9]]  →  [1,0,0,0]
[[1,9,9]]  →  [2,0,0]
JavaScript · 解法代码function plusOne(digits) { for (let i = digits.length - 1; i >= 0; i--) { // 从最低位往高位 if (digits[i] < 9) { digits[i]++; // 这一位不是 9:加 1 就结束,不需要进位 return digits; } digits[i] = 0; // 是 9:变成 0,继续向前进位 } return [1, ...digits]; // 每一位都是 9(如 999):最前面补一个 1 }
真实实测 · JavaScript · 前几组用例的输出
$ node run.js < cases.jsonl
[[1,2,3]]  →  [1,2,4]
[[4,3,2,1]]  →  [4,3,2,2]
[[9]]  →  [1,0]
[[0]]  →  [1]
[[9,9,9]]  →  [1,0,0,0]
[[1,9,9]]  →  [2,0,0]
C++ · 解法代码#include <vector> using namespace std; class Solution { public: vector<int> plusOne(vector<int>& digits) { for (int i = (int)digits.size() - 1; i >= 0; i--) { // 从最低位往高位 if (digits[i] < 9) { digits[i]++; // 这一位不是 9:加 1 就结束,不需要进位 return digits; } digits[i] = 0; // 是 9:变成 0,继续向前进位 } digits.insert(digits.begin(), 1); // 每一位都是 9(如 999):最前面补一个 1 return digits; } };
真实实测 · C++ · 前几组用例的输出
$ clang++ -std=c++17 -O2 -o bin sol.cpp && ./bin < cases.jsonl
[[1,2,3]]  →  [1,2,4]
[[4,3,2,1]]  →  [4,3,2,2]
[[9]]  →  [1,0]
[[0]]  →  [1]
[[9,9,9]]  →  [1,0,0,0]
[[1,9,9]]  →  [2,0,0]
真实实测 · 本机执行输出
共 12 组用例(含边界),以暴力解的结果为基准逐组比对:
  Python      通过 12/12
  Java        通过 12/12
  Go          通过 12/12
  JavaScript  通过 12/12
  C++         通过 12/12

随机对拍

Python 版本另外和大整数做了随机对拍:长度 1 到 12,首位不为 0(按题目保证),其余位偏向 9,这样有大量连续进位:

真实实测 · 本机执行输出
随机对拍 20000 组(长度 1 到 12 的数字数组,首位不为 0(按题目保证;偏向 9 的居多,制造大量连续进位),偶尔取单个 0),solve 与 brute 的结果全部一致。

另外四种语言只在上面 12 组固定用例上验证过,其中包含 20 位的大数,没有做大规模随机对拍。最坏情形(全是 9)也只需要扫描一遍,是线性的,所以没有做规模对比实验。

分步演示:[1, 9, 9] 加一

下面的演示用 [1, 9, 9]:最后两位是 9,依次变成 0 并向前进位;到了第一位是 1,不是 9,加 1 变成 2,结束。结果 [2, 0, 0]。每一帧都是把上面的解法在这组输入上真实执行记录下来的,运行器还检查了演示的最终结果与 solve 的返回值一致。

加一:[1, 9, 9]
正在处理的位刚被修改的位

    参考与说明

    • 官方题目页:LeetCode 66. 加一。本文只用自己的话概括了题意,完整约束以官方页面为准。
    • 数据来自本机真实运行,五种语言的版本写在页脚。
    • 没有覆盖:Java、C++、Go 里各自的整数类型溢出(本文没有对它们做转成整数的实验,只用了 JavaScript 作为例子)。
    京ICP备2026067502号-1京公网安备11010502063487号