题目描述
原题链接:https://leetcode.cn/problems/palindrome-number/description/
给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。
回文数
是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
- 例如,121 是回文,而 123 不是。
示例 1:
输入:x = 121
输出:true
示例 2:
输入:x = -121
输出:false
解释:从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
示例 3:
输入:x = 10
输出:false
解释:从右向左读, 为 01 。因此它不是一个回文数。
- 提示:
-2^31 <= x <= 2^31 - 1
进阶:你能不将整数转为字符串来解决这个问题吗?
解法一:暴力破解
根据回文数的特征,对任意大于两位的数字,对称位置上的两个数字相等即可,按照这种思路很容易想到循环比较对称位置上的数字的方式去实现,但由于无法直接对比对称位数字,就需要将数字转为字符串或字符数组(二者差别不大)进行比较判断,这种方式会额外增加空间,且数字转字符串也会增加耗时,是暴力破解的方式。
class Solution {
public boolean isPalindrome(int x) {
if(x < 0) {
return false;
}
if(x < 10) {
return true;
}
// 将数字转为字符串
String s = x + "";
// 再转为字符数组
char[] chars = s.toCharArray();
// 因为只需要比较数字两边对称的部分,因此只需遍历 chars.length / 2 次,对数字长度为偶数和奇数都适用
// 第[i]位和第 [chars.length-1-i]的数字为一对,如果每一对数字都相同则为回文数,否则只要有一对不相等,则不是回文数
// 该方法利用了数组可以随机查询任意位置的数字的特性暴力循环判断
for(int i = 0; i < chars.length / 2; i++) {
if(chars[i] != chars[chars.length - 1 - i]) {
return false;
}
}
return true;
}
}
思路二:全翻转比较
另一种思路是既然数字从前往后读和从后往前读是一样的,那只要将数字全部翻转过来,与原来的数字进行比较,如果两数相等则肯定就是回文数了。这种方式理解起来最简单直接,如果知道数字怎么翻转,实现起来也很简单。需要注意的是,因为要比较翻转前后的数字,所以不能直接修改原数x
,在翻转前需要替换原数字为新数进行翻转,然后将翻转结果与原数字比较。
class Solution {
public boolean isPalindrome(int x) {
if(x < 0) {
return false;
}
if(x < 10) {
return true;
}
// 将数字完全翻转
int r = 0;
int temp = x;
while (temp != 0) {
r = r * 10 + temp % 10;
temp /= 10;
}
// 翻转后的数字如果与原数字相等,则肯定为回文数
return r == x;
}
}
用这种方式提交后可以通过 LeetCode 的测试用例,但是这种情况存在翻转后的数字超出最大数范围的可能。无论从时间复杂度(O(n))还是准确性上来说都不是最优解。
要理解这种方式,就需要知道如何将一个整数进行翻转。思路可以用下面一张图解释:
整体的代码如下:
public int reverseNumber(int num) {
int i = 0;
while (num != 0) {
i = i * 10 + num % 10;
// 移位
num = num / 10;
}
return i;
}
思路三:半翻转比较
这种算法是最优的,但可惜我没想到,是在看了 LeetCode 官方题解后才知道的,但在思考的过程中也曾擦肩而过 😃
既然是比较对称位上的数字,那何不只取一半的数字进行比较呢,这样耗时更少且不会出现溢出的可能。例如,对于数字 1221这种偶数位数字,只需翻转取出后两位为12
,其和前两位数12
相等,即为回文数;又对于12321奇数位数字,翻转取出后两位12
,不考虑中间位数字3
,其和前两位12
也相等,是回文数。
但是数字不能轻易像字符串那样知道总共有多少位,如何知道翻转到一半呢?这个问题是这种解法的核心所在。
LeetCode官方题解的思路是:“由于整个过程我们不断将原始数字除以 10,然后给反转后的数字乘上 10,所以,当原始数字小于或等于反转后的数字时,就意味着我们已经处理了一半位数的数字了。” 这个想法很新奇(个人脑子认为),它认为已经翻转的数字位数大于或等于未翻转的数字位数,则肯定已经翻转了一半或超过一半,就停止翻转。此时,如果数字长度为偶数位,则只要翻转后的数字与还未翻转的数字相等即是回文数;如果数字长度为奇数,只要再去掉翻转后数字的最后一位,也就是原本数字的中间一位,其如果与还未翻转的数字相等,则是回文数。如上图(图片摘取自LeetCode官方题解),这种方法无需将数字转为字符串额外增加空间,而且由于是翻转一半,效率更高,肯定也不会溢出。代码如下:
class Solution {
public boolean isPalindrome(int x) {
if(x < 0 || (x % 10 == 0 && x != 0)) {
return false;
}
int r = 0;
// 每次翻转之前,只要未翻转的数字大于已翻转的数字,则说明还要继续翻转
while (x > r) {
r = r * 10 + x % 10;
x /= 10;
}
// 翻转一半后两边数字比较有两种情况时为回文数:
// 数字长度为偶数时 r == x, 例如对于 11, 1221
// 数字长度为奇数时 r == x / 10, 例如对于 121, 12321
return r == x || r / 10 == x;
}
}