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

LeetCode 9. 回文数

判断一个整数是不是回文数:正着读和倒着读一样。最直接的做法是转成字符串再比较,题目的进阶要求是不转字符串。不转字符串的常见思路是把整数整体翻转再比较,但翻转过程可能溢出;更巧的做法是只翻转后一半:每次把 x 的最后一位移到 rev 上,直到 rev 不再小于剩下的 x,这时两半就可以直接比较,既不会溢出,也少做一半的工作。本文实测这个做法里一个很容易漏掉的边界(以 0 结尾的数),给出五种语言的实现,并把翻转过程做成分步演示。

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

题意与示例

完整题目见官方页面(链接在文末)。前几组基准用例和结果由字符串比较(转成字符串再与它的反转比较)给出。一共 16 组,覆盖:负数、0、个位数、以 0 结尾的数、奇数位和偶数位回文、32 位边界:

真实实测 · 本机执行输出
输入 [121]
输出 true
输入 [-121]
输出 false
输入 [10]
输出 false
输入 [0]
输出 true
输入 [1]
输出 true
输入 [11]
输出 true

陷阱:以 0 结尾的数

只翻转后一半的循环本身对 10 这样的数会出错:x = 10 时,第一轮 rev = 0、x = 1;因为 x = 1 仍大于 rev = 0,再来一轮,rev = 1、x = 0,此时循环结束,x == rev // 10 成立(0 == 0),于是错误地判断为回文。原因是以 0 结尾的数翻转后会以 0 开头,位数变少,"两半对称"的前提就不成立了。所以必须在一开始就排除:以 0 结尾的正数(0 本身除外)一定不是回文。下面是没有这个判断的写法与正确写法的真实结果:

真实实测 · 本机执行输出
# 没有排除 '以 0 结尾的正数' 的翻转一半写法
x = 10   : 无判断写法 -> True    solve -> False    字符串比较 -> False
x = 100  : 无判断写法 -> True    solve -> False    字符串比较 -> False
x = 1000 : 无判断写法 -> True    solve -> False    字符串比较 -> False
x = 120  : 无判断写法 -> False    solve -> False    字符串比较 -> False
x = 121  : 无判断写法 -> True    solve -> True    字符串比较 -> True

10、100、1000 三个数,没有这个判断的写法都返回 True,而它们都不是回文。这是纯粹的逻辑错误,与溢出无关。120 没有出错,是因为它翻一半之后 x = 1、rev = 2,既不相等,rev // 10 = 0 也不等于 x,说明这个错误只在某些以 0 结尾的输入上出现,随手试几个数很容易漏掉。

只翻转后一半:五种语言的实现

循环条件 x > rev 表示"rev 还没有翻到一半";循环结束后,如果位数是偶数,两半应该相等,即 x == rev;如果位数是奇数,rev 会多出中间那一位,要去掉它再比较,即 x == rev // 10。Python、Java、Go、JavaScript、C++ 五种语言都是真实编译运行的;JavaScript 的除法要显式向下取整。

Python · 解法代码def solve(x): if x < 0 or (x % 10 == 0 and x != 0): # 负数不是回文;以 0 结尾的正数翻过来会以 0 开头,也不是 return False rev = 0 while x > rev: # 只翻转后一半:翻到 rev 不再小于剩下的 x 为止 rev = rev * 10 + x % 10 x //= 10 return x == rev or x == rev // 10 # 位数为偶数 / 奇数(奇数位时去掉中间那位)
真实实测 · Python · 前几组用例的输出
$ python3 solution.py   # 统一入口:逐行读取 cases.jsonl,调用上面的 solve,打印 JSON
[121]  →  true
[-121]  →  false
[10]  →  false
[0]  →  true
[1]  →  true
[11]  →  true
Java · 解法代码class Solution { public boolean isPalindrome(int x) { if (x < 0 || (x % 10 == 0 && x != 0)) return false; // 负数、以 0 结尾的正数都不是回文 int rev = 0; while (x > rev) { // 只翻转后一半 rev = rev * 10 + x % 10; x /= 10; } return x == rev || x == rev / 10; // 位数为偶数 / 奇数 } }
真实实测 · Java · 前几组用例的输出
$ javac -d build Main.java Solution.java && java -cp build Main < cases.jsonl
[121]  →  true
[-121]  →  false
[10]  →  false
[0]  →  true
[1]  →  true
[11]  →  true
Go · 解法代码func isPalindrome(x int) bool { if x < 0 || (x%10 == 0 && x != 0) { // 负数、以 0 结尾的正数都不是回文 return false } rev := 0 for x > rev { // 只翻转后一半 rev = rev*10 + x%10 x /= 10 } return x == rev || x == rev/10 // 位数为偶数 / 奇数 }
真实实测 · Go · 前几组用例的输出
$ go build -o bin . && ./bin < cases.jsonl
[121]  →  true
[-121]  →  false
[10]  →  false
[0]  →  true
[1]  →  true
[11]  →  true
JavaScript · 解法代码function isPalindrome(x) { if (x < 0 || (x % 10 === 0 && x !== 0)) return false; // 负数、以 0 结尾的正数都不是回文 let rev = 0; while (x > rev) { // 只翻转后一半 rev = rev * 10 + (x % 10); x = Math.floor(x / 10); } return x === rev || x === Math.floor(rev / 10); // 位数为偶数 / 奇数 }
真实实测 · JavaScript · 前几组用例的输出
$ node run.js < cases.jsonl
[121]  →  true
[-121]  →  false
[10]  →  false
[0]  →  true
[1]  →  true
[11]  →  true
C++ · 解法代码class Solution { public: bool isPalindrome(int x) { if (x < 0 || (x % 10 == 0 && x != 0)) return false; // 负数、以 0 结尾的正数都不是回文 int rev = 0; while (x > rev) { // 只翻转后一半 rev = rev * 10 + x % 10; x /= 10; } return x == rev || x == rev / 10; // 位数为偶数 / 奇数 } };
真实实测 · C++ · 前几组用例的输出
$ clang++ -std=c++17 -O2 -o bin sol.cpp && ./bin < cases.jsonl
[121]  →  true
[-121]  →  false
[10]  →  false
[0]  →  true
[1]  →  true
[11]  →  true
真实实测 · 本机执行输出
共 16 组用例(含边界),以暴力解的结果为基准逐组比对:
  Python      通过 16/16
  Java        通过 16/16
  Go          通过 16/16
  JavaScript  通过 16/16
  C++         通过 16/16

随机对拍

Python 版本另外和字符串比较做了随机对拍:

真实实测 · 本机执行输出
随机对拍 30000 组(四成是构造出来的回文(奇偶位数都有),二成是 32 位范围内的随机整数(含负数),四成是 0 到 2000 的小整数(含很多以 0 结尾的数)),solve 与 brute 的结果全部一致。

另外四种语言只在上面 16 组固定用例上验证过,其中包含以 0 结尾的数和 32 位边界,没有做大规模随机对拍。

分步演示:只翻转后一半

下面的演示用 x = 12321(五位,奇数位回文)。每一帧都是把上面的解法在这个输入上真实执行记录下来的,运行器还检查了演示最终得到的结果与 solve 的返回值一致。翻转结束时 x = 12、rev = 123:rev 多出了中间那位 3,所以要用 x 和 rev // 10 = 12 比较,结果相等。

回文数:x = 12321(不转字符串,只翻转后一半)
刚更新的值

    参考与说明

    • 官方题目页:LeetCode 9. 回文数。本文只用自己的话概括了题意,完整约束以官方页面为准。
    • 数据来自本机真实运行,五种语言的版本写在页脚。
    • 没有覆盖:把整数整体翻转再比较的做法(会碰到溢出问题,与第 7 题相同);用字符串比较的做法本文只用作核对基准。
    京ICP备2026067502号-1京公网安备11010502063487号