LeetCode——回文素数
题目描述:
求出大于或等于 N 的最小回文素数。
回顾一下,如果一个数大于 1,且其因数只有 1 和它自身,那么这个数是素数。
例如,2,3,5,7,11 以及 13 是素数。
回顾一下,如果一个数从左往右读与从右往左读是一样的,那么这个数是回文数。
例如,12321 是回文数。
示例 1:
输入:6
输出:7
示例 2:
输入:8
输出:11
示例 3:
输入:13
输出:101
提示:
1 <= N <= 10^8
答案肯定存在,且小于 2 * 10^8。
思路:
回文数: 一个数在反转得到的数值后与原来的数值相等,则该数为回文数。
算法判断: 判断回文数的方法大概可以分为两种。
一、可以将该数转换为字符串,设置指针分别指向首尾,若两指针指向的值相等是,两指针往中间移动,也就是首指针往后移动,尾指针往前移动。如果出现两个值不等,则表示该数不是回文数,结束循环,做出判断;当首指针的位置大于尾指针的位置时跳出循环,此时可以判断出该数为回文数。
二、直接使用该数进行判断,先保存该数,然后将该数拆开来重组,大概的做法就是每次将存放重组后的数乘10再加上该数取余,然后该数再除去10,如此循环操作,当该数为0时跳出循环,最后比较原来的数和存放重组后的数,如果相等,则表示要判断的数为回文数,反之则不是。
素数: 一个数除了1和自身外没有其他因数的数,称为素数。
算法判断: 大致做法就是将要判断的数除去从2到自身的数之前之间的所有数,利用循环可以很简单的实现。在此基础上可以进行优化,具体优化后面展示和分析。
对于这道题的做法,相信很多人的第一想法都会是直接暴力破解:从输入的数开始,先判断该数是否是回文数,再进行判断该数是否是素数,如果都不是,则该数自加1,再进入判断,如此循环,直到找到。
这个想法,对于该题来说,基本的思想是对的,不过如果没有进行细节的优化,在提交的时候会发现在输入是(N=9989900)时,时间超限了,这是因为该数不是回文素数,而从该数到下个回文素数之间的间距很大,导致要进行很多次的判断,而素数的判断和回文数的判断在大规模数据判断时,耗时会非常大,所以导致了时间超限。
优化: 首先可以对素数的判断上进行优化,常见的优化就是将循环到自身停止的条件改为循环到自身开根后一个数停止,这样一来时间复杂度会从 O ( N ) O(N) O(N)降低到 O ( N ) O(\sqrt{N}) O(N),这样一来素数的判断速度会提升很多,但是在做完这样的优化后还是会在9989900这个部分超时。在这之后,观摩领扣上大佬们的做法后,寻找更加高效的素数判断,找到了一种6步进素数判别法,具体的证明和做法寻找于:素数的四种判断方法、实现及比较。但是在做完该优化后,还是存在时间超限的现象,所以对于素数判断的优化上还不够,领扣上的大佬们还在回文数上进行了优化。回文数的优化大概如下:通过观察回文数,发现除了11外,其他偶数长度的回文数,如22,33,1111,1221,111111…之类的都不是素数,所以可以在循环中跳过偶数长度的数。具体内容学习于领扣上一位大佬的题解:代码优化的荆棘之路。经过这一步优化后,时间超限的情况就不会出现了。
关键代码:
bool isPrime(int N) {//素数判断
if (N == 2||N == 3)//2和3不符合下面判断规律,而且只有这两个,可以直接单独作判断
return true;
if (N % 6 != 1 && N % 6 != 5)//如果该数除以6后的余数不是1也不是5,则该数不是素数
{
return false;
}
for (int i = 5; i <= sqrt(N); i+=6) {//从5开始除,每次跳跃的步数为6,规律和证明见于思路中的链接文章
if (N % i == 0 || N % (i + 2) == 0)
{
return false;
}
}
return true;
}
int primePalindrome(int N) {
int ret[] = { 2, 2, 2, 3, 5, 5, 7, 7, 11, 11, 11, 11};//小于等于11的数直接进行判断,用数组存放起0到11之间每个数对应能够得到的回文素数
if (N < 12)
return ret[N];
if ((N & 1) == 0)//如果该数为偶数,则该数必然不是素数,则将该数自加变为奇数,这里用位运算会比较快
++N;
for (int i = N;; i += 2)//每次步数为2,跳过了偶数
{
if (i % 6 != 1 && i % 6 != 5) {//如果该数除以6后的余数不是1或5,则该数不是素数,不用判断该数是否为回文数,直接跳过
continue;
}
if (isPrime(i))//如果该数是素数,则进行回文数的判断
{
/*char temp[25];
snprintf(temp, sizeof(temp), "%d",i);//将该数转换为字符数组
string str(temp);//将字符数组转换为字符串
int len = str.length();
if ((len & 1) == 0)//判断该数长度,如果长度为偶数,则直接将该数置到下个数量级
{
i = pow(10, len) + 1;
i -= 2;
continue;
}
int j;
for (j = 0; j < len >> 1; ++j)//回文数判断
{
if (str[j] != str[len - 1 - j])
{
break;
}
}
if (j == len >> 1)//如果不是break跳出循环的,则该数为回文数
{
return i;
}*///这一部分是字符串判断回文数的做法
int rev = 0,m = i;//这一部分是反转该数进行回文数判断的做法,rev存放反转后的数
int len = 0;//用于存放该数的长度
for(;m;)//获取反转后的数
{
rev = rev*10+(m%10);
m /= 10;
++len;
}
if(rev == i)//反转后的数与原值相等,则该数为回文数
return i;
if ((len & 1) == 0)
{
i = pow(10, len) + 1;
i -= 2;
continue;
}
}
}
}
总结:
对我来说,通过这道题和观摩大佬们的代码,我真的学到了很多,这道题领扣上的大佬们还有更加高效的做法,就是通过字符串构造回文数,然后转换为整型,再判断构造后的数是否为素数以及是否比原值大,如果都满足,则所构造的回文数为回文素数,具体做法可以到领扣上观摩,csdn上也有此类的做法。