目录

题目描述

7. 整数反转

image-20250510070143274

题意分析

输入是一个 32 位有符号整数 x,要求把它的十进制数位左右颠倒后返回。真正的难点不在「颠倒」,而在于颠倒之后的值可能已经不是一个 32 位整数了,这时必须返回 0

题面里有一句决定性的约束信号:假设运行环境只能存储 32 位有符号整数,范围是 $[-2^{31}, 2^{31} - 1]$。这句话的意思是「不允许先算出结果再回头检查」——因为一旦算出来,那个越界的中间值本身就已经不存在了,你手上拿到的是一个被回绕过的错误数字。所以判断必须发生在计算之前。

三个边界要在读题阶段就想清楚。第一,负号不参与反转,-123 的答案是 -321 而不是 321-,符号原样保留。第二,末尾的零在反转后会跑到最高位,而最高位的零是不存在的,所以 120 的答案是 21,位数会变少,这不是 bug 而是十进制的固有性质。第三,x 本身可能是 0,此时循环一次都不进,直接返回 0

还有一个容易被忽略的不对称:-2147483648 的绝对值超出了正数上界,所以任何「先取绝对值、算完再补符号」的写法在这个输入上都会当场翻车。安全的做法是让负数从头到尾保持负数。

解法:逐位弹出并提前判溢出

核心思路

问题关键:逐位反转本身很简单,难点是题目只允许使用 32 位整数。ans * 10 + digit 一旦先发生溢出,事后看到的已是回绕后的错误值,因此必须在运算前判断。

为什么选逐位弹出digit = x % 10 取出最低位,x /= 10 删除最低位,再用 ans = ans * 10 + digit 将它追加到结果。Java 和 Go 对负数除法都向 0 截断,负数可以沿用同一套逻辑,不必先取绝对值。

不变量:每轮开始时,ans 是已弹出数位的反转结果,并且仍在 32 位范围内;x 保存尚未处理的高位。追加新数位前,先保证 ans * 10 + digit 不会越界。

边界推导:正向越界条件是 ans > MAX/10,或 ans == MAX/10digit > 7;负向对应 ans < MIN/10,或相等时 digit < -8。这些比较只使用尚未溢出的值。

正确性:每轮把当前最低位追加到 ans 末尾,因此处理 t 位后,ans 恰是原数末尾 t 位的逆序;循环结束时所有数位都已处理,得到完整反转值。若某轮触发边界条件,该次追加必然超出 32 位范围,按题意返回 0。

解题步骤

  • 初始化 ans = 0,当 x != 0 时循环;该条件同时覆盖正数和负数。
  • 先用 % 10 取最低位,再用 /= 10 删除该位。
  • 根据 MAX/10MIN/10 及末位 7、-8,判断追加后是否溢出;溢出立即返回 0。
  • 安全时执行 ans = ans * 10 + digit,循环结束返回 ans
  • 口述样例-123 依次弹出 -3、-2、-1ans 依次为 -3、-32、-3211534236469 在最后一次追加前触发上界判断,返回 0;120 得到 21,前导零自然消失。

代码实现

class Solution {
    public int reverse(int x) {
        int ans = 0;
        while (x != 0) {
            int digit = x % 10;
            x /= 10;
            if (ans > Integer.MAX_VALUE / 10
                    || (ans == Integer.MAX_VALUE / 10 && digit > 7)) {
                return 0;
            }
            if (ans < Integer.MIN_VALUE / 10
                    || (ans == Integer.MIN_VALUE / 10 && digit < -8)) {
                return 0;
            }
            ans = ans * 10 + digit;
        }
        return ans;
    }
}
const (
    maxInt32 = 1<<31 - 1
    minInt32 = -1 << 31
)

func reverse(x int) int {
    ans := 0
    for x != 0 {
        digit := x % 10
        x /= 10
        if ans > maxInt32/10 || (ans == maxInt32/10 && digit > 7) {
            return 0
        }
        if ans < minInt32/10 || (ans == minInt32/10 && digit < -8) {
            return 0
        }
        ans = ans*10 + digit
    }
    return ans
}

复杂度分析

  • 时间复杂度:$O(\log \lvert x\rvert)$,循环次数等于十进制位数;32 位整数最多 10 位。
  • 空间复杂度:$O(1)$,只使用固定数量的整数变量。

关键点总结

  • 溢出检查必须在乘 10 之前,并同时覆盖“商越界”和“商相等但末位越界”。
  • 不要对 Integer.MIN_VALUE 取绝对值,它的正数值无法用 32 位 int 表示。
  • Java、Go 的 % 会保留被除数符号,因此负数无需单独处理。
  • 面试若允许 64 位中间变量,可以先用 long 计算再判断;本写法严格满足题目的 32 位限制。

易错点总结

  • 先计算再判溢出1534236469 会先发生整数回绕,之后已无法恢复正确结果。
  • 遗漏相等时的末位判断:通用 32 位累积模板必须检查上界末位 7 和下界末位 -8。
  • 先取绝对值Math.abs(Integer.MIN_VALUE) 仍是负数,不能用这种方式统一符号。
  • 循环条件写成 x > 0-123 将直接返回 0;应写 x != 0
  • 先除 10 再取模:会丢掉原来的最低位,123 将错误得到 21。

相似题目

题目 难度 考察点
9. 回文数 简单 只反转一半数位即可比较,无需处理溢出
8. 字符串转换整数 (atoi) 中等 溢出时钳制到边界值而非返回 0,还需解析前后缀
190. 颠倒二进制位 简单 位宽固定为 32,反转的是二进制位且不会溢出
29. 两数相除 中等 禁用乘除法,靠倍增减法逼近商
66. 加一 简单 数位存在数组里,关注进位导致的扩容
43. 字符串相乘 中等 用数组模拟竖式,彻底回避整数范围限制