LeetCode : 866. 迴文素數(Prime Palindrome)分析和解答
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
數學規律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;
}
}