给定一个 32 位有符号整数 x,返回把它的数字部分反转后的结果;如果反转后超出 32 位有符号整数的范围 [-2^31, 2^31 - 1],返回 0。题目还规定环境不允许存储 64 位整数,所以不能先用更宽的类型算出结果再比较。这道题没有值得做成动画的动态过程,难点全在溢出检查和各语言对整数运算的细节差异,所以本文给出 Python、Java、Go、JavaScript、C++ 五种语言的实现,每一种都真实编译运行,并与一个独立的暴力解逐组对比。
完整题目见官方页面(链接在文末)。前几组基准用例及暴力解算出的结果(暴力解是把整数转成字符串翻转,再用 Python 的大整数直接判断范围,与下面的解法思路完全不同);一共 15 组,覆盖零、个位数、末尾带 0、负数、32 位上下界,以及翻转后刚好在边界内外的数:
输入 [123] 输出 321 输入 [-123] 输出 -321 输入 [120] 输出 21 输入 [0] 输出 0 输入 [-10] 输出 -1 输入 [7] 输出 7
按位取出个位、拼到结果末尾,这个循环谁都会写:rev = rev * 10 + x % 10。问题出在 rev * 10 可能已经超出 32 位。下面是不做检查的 Java 写法在两个会溢出的输入上的真实输出,以及 Java 里 Math.abs 对最小值的结果:
$ javac N.java && java N # 不做溢出检查的写法 naive(1534236469) = 1056389759 naive(-1563847412) = 2147483645 Math.abs(Integer.MIN_VALUE) = -2147483648
1534236469 翻转后应该是 9646324351,已经超出 32 位范围,正确答案是 0,但不检查的写法返回了 1056389759,这是 9646324351 按 32 位回绕后的值,看上去像一个正常的答案,不会报任何错。-1563847412 翻转后是 -2147483651,比下界还小一点,无检查的写法返回的却是正数 2147483645,连符号都变了。第三行说明另一个坑:Math.abs(Integer.MIN_VALUE) 的结果仍然是 -2147483648,因为 2147483648 装不进 32 位,所以在 Java、C++ 这类固定宽度语言里,"先取绝对值再处理"对 -2147483648 这个输入是不可靠的。
Python 没有这两个问题,因为整数不会溢出。同样的输入在 Python 里翻转的结果是:
$ python3 -c "print(int(str(1534236469)[::-1]))" 9646324351
9646324351 大于 2147483647,所以最终答案是 0。这也是暴力解的思路:不检查过程,直接用大整数算完再判断范围。但题目要求的做法是过程中始终不超出 32 位,所以各种语言的实现都要在"乘 10 加个位"之前就判断会不会溢出。
思路:每次取出末位 d 和去掉末位的 x,在执行 rev * 10 + d 之前,判断它会不会越界。rev 越界的条件是:rev 已经大于 INT_MAX / 10,或者等于 INT_MAX / 10(即 214748364)且 d 大于 7;下界同理,d 小于 -8。Java、Go、C++、JavaScript 里 % 的结果符号跟随被除数,/ 向 0 取整,所以负数不需要特殊处理,直接对 x 做上面的循环;Python 的 % 和 // 向负无穷取整,行为不同,所以 Python 版本先记下符号,对绝对值做循环(Python 里取绝对值不会溢出)。JavaScript 的数字是双精度浮点数,没有 / 向 0 取整的整数除法,要显式用 Math.trunc,不能用 Math.floor。
Python · 解法代码def solve(x): INT_MAX = 2**31 - 1 sign = -1 if x < 0 else 1 x = abs(x) rev = 0 while x: x, d = divmod(x, 10) # 乘 10 之前先检查,rev 始终留在 32 位范围内,不依赖 Python 的大整数 if rev > INT_MAX // 10 or (rev == INT_MAX // 10 and d > (7 if sign > 0 else 8)): return 0 rev = rev * 10 + d return sign * rev
$ python3 solution.py # 统一入口:逐行读取 cases.jsonl,调用上面的 solve,打印 JSON [123] → 321 [-123] → -321 [120] → 21 [0] → 0 [-10] → -1 [7] → 7
Java · 解法代码class Solution { public int reverse(int x) { int rev = 0; while (x != 0) { int d = x % 10; // 余数的符号跟随被除数,负数得到负的个位 x /= 10; if (rev > Integer.MAX_VALUE / 10 || (rev == Integer.MAX_VALUE / 10 && d > 7)) return 0; if (rev < Integer.MIN_VALUE / 10 || (rev == Integer.MIN_VALUE / 10 && d < -8)) return 0; rev = rev * 10 + d; } return rev; } }
$ javac -d build Main.java Solution.java && java -cp build Main < cases.jsonl [123] → 321 [-123] → -321 [120] → 21 [0] → 0 [-10] → -1 [7] → 7
Go · 解法代码func reverse(x int) int { const maxInt32, minInt32 = 1<<31 - 1, -(1 << 31) rev := 0 for x != 0 { d := x % 10 // 余数的符号跟随被除数 x /= 10 if rev > maxInt32/10 || (rev == maxInt32/10 && d > 7) { return 0 } if rev < minInt32/10 || (rev == minInt32/10 && d < -8) { return 0 } rev = rev*10 + d } return rev }
$ go build -o bin . && ./bin < cases.jsonl [123] → 321 [-123] → -321 [120] → 21 [0] → 0 [-10] → -1 [7] → 7
JavaScript · 解法代码function reverse(x) { const MAX = 2 ** 31 - 1, MIN = -(2 ** 31); const maxQ = Math.trunc(MAX / 10), minQ = Math.trunc(MIN / 10); let rev = 0; while (x !== 0) { const d = x % 10; // 余数的符号跟随被除数 x = Math.trunc(x / 10); // 向 0 取整,不能用 Math.floor if (rev > maxQ || (rev === maxQ && d > 7)) return 0; if (rev < minQ || (rev === minQ && d < -8)) return 0; rev = rev * 10 + d; } return rev; }
$ node run.js < cases.jsonl [123] → 321 [-123] → -321 [120] → 21 [0] → 0 [-10] → -1 [7] → 7
C++ · 解法代码#include <climits> class Solution { public: int reverse(int x) { int rev = 0; while (x != 0) { int d = x % 10; // C++11 起,商向 0 取整,余数符号跟随被除数 x /= 10; if (rev > INT_MAX / 10 || (rev == INT_MAX / 10 && d > 7)) return 0; if (rev < INT_MIN / 10 || (rev == INT_MIN / 10 && d < -8)) return 0; rev = rev * 10 + d; } return rev; } };
$ clang++ -std=c++17 -O2 -o bin sol.cpp && ./bin < cases.jsonl [123] → 321 [-123] → -321 [120] → 21 [0] → 0 [-10] → -1 [7] → 7
五种语言都通过了同一批 15 组用例,并且每一组的输出都与暴力解的结果完全一致:
共 15 组用例(含边界),以暴力解的结果为基准逐组比对: Python 通过 15/15 Java 通过 15/15 Go 通过 15/15 JavaScript 通过 15/15 C++ 通过 15/15
Python 版本另外做了大规模随机对拍:
随机对拍 20000 组(一半是 32 位范围内均匀随机的整数,另一半是首位为 1 或 2 的 10 位数(正负各半),专门逼近翻转后溢出的边界),solve 与 brute 的结果全部一致。
另外四种语言只在这 15 组固定用例上验证过,没有做大规模随机对拍;15 组用例里包含了 32 位上下界和翻转后刚好在边界内外的输入,是针对上面溢出判断挑选的。