
图中把整数拆成尚未处理的高位与逐步反转的低位,比较发生在两段相遇时。
判断 12321 是不是回文,转成字符串再反转当然直观。但如果题目限制不能使用字符串,真正的考点就变成了:怎样取出末位、怎样缩短原数,以及怎样避免为了检查一个数,先把整个数反转到溢出。
先说明数字回文的口径
这里判断普通十进制整数的写法是否从两端读都一样,不把前导零作为数字的一部分。负数直接判定为不是回文,因为负号只在一侧;零本身是回文。一个非零整数若末位为零,也不可能是回文,否则它的开头还得有零,与通常十进制表示矛盾。
这一步看似只是边界,其实给后面的停止条件提供了保障。像 10、100 这样的数先被排除,反转末尾时就不会因为零丢失而把两段长度关系搞乱。输入若是带符号字符串,则还涉及解析和前导零政策,已经是另一道接口题。
每次只拿走最后一位
对非负整数,除以十取整,可以去掉末位;对十取余,可以得到末位。设置一个变量 left 保存还没处理的高位,一个变量 right 保存已经反转的低位,每轮执行:right 变成 right×10 加上 left 的末位,随后 left 去掉这一位。
以 1221 为例,初始为 left=1221、right=0。第一轮后是 122 和 1,第二轮后是 12 和 12。两段相等,说明原数前半与后半镜像相同,不必再把剩下部分全部反转。
以 12321 为例,过程是 1232 与 1、123 与 12、12 与 123。此时右边已经多拿了一位,中间那个 3 不需要和任何位置配对,把 right 再除以十取整,就得到 12,可以与 left 比较。

停在两段交会处
循环条件可以设为 left 大于 right。对于尚未交会的有效输入,剩余高位仍比已反转的低位更长,或数值还未达到相遇条件;继续移动,直到左侧不再大于右侧。最后接受两种情况:left 等于 right,或者 left 等于 right 除以十的整数部分。
前一种对应偶数位,后一种允许奇数位忽略中间数字。有的非回文数也可能在较早时候让 left 不大于 right,但最终的相等检查会拒绝它,并不需要为了分类位数而单独统计总位数。
下面的函数不使用字符串,适合直接核对状态变化:
def is_palindrome(x):
if x < 0 or (x != 0 and x % 10 == 0):
return False
right = 0
while x > right:
right = right * 10 + x % 10
x //= 10
return x == right or x == right // 10
为什么只反转一半更稳妥
完整反转一个固定宽度整数时,原数合法不代表反转后的数仍能放进同一种有符号类型。先做乘十再检查,有可能在检查前就触发语言层面的溢出问题。只反转约一半数字,可以显著缩小中间值;仍应结合输入位宽和中间变量类型论证边界。
上面的 Python 整数不是固定宽度,因此代码没有模拟机器整数溢出。若改写为 C++ 或 Java,应明确支持的输入范围,选择足够宽的中间类型,而不是把 Python 的运行结果当成所有语言的证明。算法思想相同,数值模型不同。
原数在函数内被逐步缩短,因此若调用方还需要最初的值,应在进入循环前保存副本。这里改变的是局部整数变量,不会修改调用方保存的那个数字。
用边界和独立答案核对
至少检查零、一位数、负数、末尾零、奇数位回文、偶数位回文,以及相差一个末位的非回文。自设例子可以用 0、7、-121、10、1221、12321、12331。允许在测试程序中用字符串生成独立参考结果,因为限制的是待实现算法,不是测试参照。
复杂度随十进制位数线性增长,常写为 O(log x);额外只维护两个整数,在固定宽度机器模型下记为 O(1)。若讨论任意精度超大整数,除法和乘法本身的代价也要纳入,不能把每次大整数运算都当作固定成本。
这道题的关键不是巧妙躲开字符串,而是看见对称结构后,只处理判断所需的信息。你会怎样解释最后一次比较中,为什么可以丢掉 right 的末位,却不能随意丢掉 left 的末位?