判断一个整数是不是回文数:正着读和倒着读一样。最直接的做法是转成字符串再比较,题目的进阶要求是不转字符串。不转字符串的常见思路是把整数整体翻转再比较,但翻转过程可能溢出;更巧的做法是只翻转后一半:每次把 x 的最后一位移到 rev 上,直到 rev 不再小于剩下的 x,这时两半就可以直接比较,既不会溢出,也少做一半的工作。本文实测这个做法里一个很容易漏掉的边界(以 0 结尾的数),给出五种语言的实现,并把翻转过程做成分步演示。
完整题目见官方页面(链接在文末)。前几组基准用例和结果由字符串比较(转成字符串再与它的反转比较)给出。一共 16 组,覆盖:负数、0、个位数、以 0 结尾的数、奇数位和偶数位回文、32 位边界:
输入 [121] 输出 true 输入 [-121] 输出 false 输入 [10] 输出 false 输入 [0] 输出 true 输入 [1] 输出 true 输入 [11] 输出 true
只翻转后一半的循环本身对 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 # 位数为偶数 / 奇数(奇数位时去掉中间那位)
$ 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; // 位数为偶数 / 奇数 } }
$ 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 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); // 位数为偶数 / 奇数 }
$ 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; // 位数为偶数 / 奇数 } };
$ 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 比较,结果相等。