866. 回文素数
求出大于或等于 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
。
解答
思路:
判断素数的方法和判断回文数的方法都不难,难点在于怎么在短时间内完成,即不能超时。
起先我只是想办法优化寻找素数的方法,然后再去判断是否是回文数。然后发现一直会超时,原因是数字很大后,这样执行的效率很低。这题主要的优化策略在判断回文数上了,而素数的优化起到的是辅助性的增强。
数学规律1:除 11 外的偶数位回文数,都能被 11 整除
能被 11 整数的数字有个特点:它们的偶数位的和,等于他们奇数位的和。这是一条已经被证明的定理了,想要详细了解的话可以另行百度。
而偶数位的回文数,由于回文数的特点,以及它又是偶数位,所以它偶数位的和一定等于奇数位的和,即它能被 11 整除,所以除了 11 其余的都不是素数。
结论: 我们可以跳过所有位数为偶数的数字,除了 11。例如当输入 123456 时,我们可以直接从 1000001 开始查找。
数学规律2:除 2 和 3 外,所有的素数一定在 6 的两侧
什么意思呢,也就是说除了 2 和 3,其余素数一定等于 6x-1
或 6x +1
,其中 x >= 1。
首先 6x
肯定不是质数,因为它能被 6 整除;其次 6x+2
肯定也不是质数,因为它还能被 2 整除;依次类推,6x+3
肯定能被 3 整除;6x+4
肯定能被 2 整除;6x+5
就等同于 6x-1
。
结论: 我们可以将遍历数字时的步长再增大点,而不是只过滤偶数。其次,在判断数字是否为素数时,我们也只需要判断该数字能否被 6 两侧的数字整除即可。(不少解法只过滤偶数,这样以 6 个数为单位,需要判断剩下的 3 个,而该方法只需要判断剩下的 2 个,效率提升 30% 左右)
注意: 你也可以选择只过滤偶数,同样可以在指定时间内完成。虽然会慢些,但是代码逻辑会简单不少。
其余优化
- 先使用上述两条数学规律过滤掉大量情况,这样我们需要实际计算的数字就少很多很多了。
- 其次对于满足上述两种条件的数字,我们先判断它是否是回文数,因为判断是回文数的效率会比判断是素数的效率高很多。
- 由于 11 是个特别的情况(同理还有 2 和 3),我不想在每次位数为偶数时还去判断它是不是 11,所以我选择直接把 11 以下的数单独处理了,它们也不多。
实现
代码中又多加了一些注释,应该不难理解了。
/**
* Copyright © 2018 by afei. All rights reserved.
*
* @author: afei
* @date: 2018年11月3日
*/
class Solution {
public static void main(String[] args) {
// 这个例子很容易超时,要保证在一秒内完成,本例方法只需 10ms
System.out.println(primePalindrome(9989900));
}
public static int primePalindrome(int N) {
if (N <= 11) { // 单独处理 11 以下的数,包括 11
if (N <= 2)
return 2;
else if (N == 3)
return 3;
else if (N <= 5)
return 5;
else if (N <= 7)
return 7;
else
return 11;
}
// when N > 11
int modulus = N % 6; // 这个用来挑选余数为 1 或 5 的数字
switch (modulus) {
case 0:
N++;
case 1:
modulus = 1;
break;
case 2:
N++;
case 3:
N++;
case 4:
N++;
case 5:
modulus = 5;
break;
}
int[] nums = new int[10]; // 这个用来判断回文数的,长度 10 就够用了
for (;;) {
int length = getNumLength(N, nums);
if ((length & 1) == 0) {
// if length is even, palindrome must be divided by 11
N = (int) Math.pow(10, length) + 1;
modulus = 5; // modulus must be 5
continue; // 跳过所有位数为偶数的数字
}
// here modulus must be 1 or 5
if (isPalindrome(length, nums) && isPrime(N)) {
return N;
}
// 步长为 2 或者 4,效率更高
if (modulus == 1) {
N += 4;
modulus = 5;
} else { // modulus == 5
N += 2;
modulus = 1;
}
}
}
public static int getNumLength(int N, int[] nums) {
int length = 0;
while (N > 0) {
nums[length++] = N % 10;
N /= 10;
}
return length;
}
public static boolean isPrime(int num) {
int sqrt = (int) Math.sqrt(num);
// We have filtered nums what "N % 6 != 1 || N % 6 != 5"
for (int i = 5; i <= sqrt; i += 6) {
// So here just check "i % 6 == 1 || i % 6 == 5"
if (num % i == 0 || num % (i + 2) == 0) {
return false;
}
}
return true;
}
public static boolean isPalindrome(int length, int[] nums) {
for (int i = 0; i < length / 2; i++) {
if (nums[i] != nums[length - i - 1]) {
return false;
}
}
return true;
}
}
项目地址
https://github.com/afei-cn/LeetCode/tree/master/866.%20Prime%20Palindrome
原题地址
https://leetcode-cn.com/problems/prime-palindrome/description/