突然发现 leetcode 上有每日一题的机制,吧唧嘴,然后我就很开心的捡了起来,然后一来就是两道困难的题目,喵喵喵,我个菜菜,到今天才写出一道简单的题目,来看看题,
![](https://i-blog.csdnimg.cn/blog_migrate/acae690c11fdddb162d0a068bd7869b3.png)
按这道题的意思最开始应该用字符串,喵喵喵,姑且认为字符串是最简单的解法吧,其实最开始我还真的没想过用字符串解,因为逆向构造就很香啊。
如果 用C语言 我们需要知道 C 的一个函数 sprintf,具体的用法自行百度,这个函数的功能就是 将 数据 按某种数据类型转换到,举个例子:
sprintf(buf, "%d", x);
这样子我们就把 x 中的值 以 %d 的 数据格式写入了 buf 中
所以这道题目的第一步就解决了,然后第二步,回文检查,检查回文就很简单了,1/2 比较就好了, 没什么难点
代码如下:
bool isPalindrome(int x){
if (x < 0)
{
return false;
}
char buf[12] = {0};
sprintf(buf, "%d", x);
int len = strlen(buf);
for (int i = 0; i < len/2; i++)
{
if (buf[i] != buf[len-i-1])
{
return false;
}
}
return true;
}
现在我们来思考一下,新的方法构造法,如我们所想 如果数字是回文,那么逆向构造后等于原数,
这里有个难点,如何知道未知长度的整型数据的每一位, 并且加和在一起呢?
我们来看两个数: 123, 4567
如果我们知道长度 那么 取各个位因该就是:
123%10,123/10 4567%10 4567/10
12%10, 12/10 456%10 456/10
1%10, 1/10 45%10 45/10
5%10 5/10
很容易就发现 我们一直重复的语句就是%10,和/10,所以 如果 数字是 a,则有
while ()
{
sum[i++] = a%10;
a /= 10;
}
那么问题来了,循环的关键问题,如何停止呢,重新来看看,上面的例子,嗯, 无论这个数值是什么,最后取完最后一位后这个值就是 0 了,所以只要a为0程序就停止了,那么代码就是这个样子了
while (a)
{
sum[i++] = a%10;
a /= 10;
}
接下来就是这个,逆向构造的核心,如何反向构造这个数, 如果a = 12345, 那么resa = 54321,很显然我们每次取余取出的都是最后一位
那么我们是不是只需要将 最后一位加到 sum 上后再乘以权重10 即可即 sum = a%10+sum*10 或者 是 sum += a%10; sum*=10;
所以代码就变成了这样
while (a)
{
sum = a%10+sum*10;
a /= 10;
}
余下来的就是这个程序需要考虑的细枝末节了,例 如 负数,肯定就是不是回文数, 10 肯定不是 回文,1010,1000这些 右边为0 的数都不是 回文数
bool isPalindrome(int x)
{
if (x < 0 || (0 == x % 10 && x != 0))
{//过滤小数, 排除 右边为 0 的数,注意不是 0
return false;
}
// 保留下 x 的原本的值
int resx = x;
int num = 0;
while (x)
{
num = x % 10 + num * 10;
x /= 10;
}
if (resx == num)
{
return true;
}
return false;
}
但是 这样能AC吗,显然不能 int 最大值为 2^31 次方 21亿多,最后一位是7,一但逆向构造那就是70多亿所以肯定不行,这时候让我们想一想,传进来的肯定是整数范围,如果是回文数那么,无论如何都不会超过整数范围, 无论是正向构造还是逆向构造都不会溢出,也就是溢出的一定不是回文数,所以需要加一个预判
最后生成代码
bool isPalindrome(int x)
{
if (x < 0 || (0 == x % 10 && x != 0))
{//过滤小数, 排除 右边为 0 的数,注意不是 0
return false;
}
// 保留下 x 的原本的值
int resx = x;
int num = 0;
while (x)
{
if (num > INT_MAX / 10)
{ //溢出判断
return false;
}
num = x % 10 + num * 10;
x /= 10;
}
if (resx == num)
{
return true;
}
return false;
}
原本这个,我想用cpp的 try catch 结果发现 没有溢出异常,拉倒了
上面这种方法用了枝减法,时间复杂度为O(n),取决于数字长度,其实还有一个O(log(n))的算法
这个是基于上面那个算法实现的,基于溢出的优化,全部逆向构造会溢出,那么咱们就逆向构造一半,如果左边等于右边那么这就是回文,
这里的最大问题是 如何到一半停下来,思考一下,假设 x 为 数据, y 为构造数据,那么 y 是不是在一直变大,x是不是一直在变小,那是不是当 x > y 的时候 while 就会执行
如果x <= y这个数是不是就是 就刚好到一半,所以 while (x > y) 就是while 的停止条件,到这里,循环就结束
while (num < x)
{
num *= 10;
num += (x % 10);
x /= 10;
}
不过我们还需要考虑一个细节,如果数据 是 1221 没有任何问题, 但是如果 12321 那么 y = 123 ,x = 21 明明是回文数 但是 x != y,凡是奇数长度的数据的都会出现这个问题所以,我们需要进行特殊处理
在判断的地方写为 if (x == num || x == y / 10) 这样子就可以去掉奇数的限制完美解决这个问题,
代码如下:
bool isPalindrome(int x)
{
if (x < 0 || (x % 10 == 0 && x != 0))
{
// 特殊情况:
// 如上所述,当 x < 0 时,x 不是回文数。
// 同样地,如果数字的最后一位是 0,为了使该数字为回文,
// 则其第一位数字也应该是 0
// 只有 0 满足这一属性
return false;
}
int resNum = 0;
while (x > resNum)
{
resNum = resNum * 10 + x % 10;
x /= 10;
}
// 当数字长度为奇数时,我们可以通过 resNum/10 去除处于中位的数字。
// 例如,当输入为 12321 时,在 while 循环的末尾我们可以得到 x = 12,resNum = 123,
// 由于处于中位的数字不影响回文(它总是与自己相等),所以我们可以简单地将其去除
return x == resNum || x == resNum / 10;
}
正如上面所说时间复杂度为log(n),只需要上面一半的时间.
目前只能想到这些方法,如有不当望指正.
leetcode不易,诸君共勉!