1. 程式人生 > >LeetCode : 866. 迴文素數(Prime Palindrome)分析和解答

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

時,我們可以直接從 1000001 開始查詢。

數學規律2:除 2 和 3 外,所有的素數一定在 6 的兩側

什麼意思呢,也就是說除了 2 和 3,其餘素數一定等於 6x-16x +1,其中 x >= 1。

首先 6x 肯定不是質數,因為它能被 6 整除;其次 6x+2 肯定也不是質數,因為它還能被 2 整除;依次類推,6x+3 肯定能被 3 整除;6x+4 肯定能被 2 整除;6x+5 就等同於 6x-1

結論: 我們可以將遍歷數字時的步長再增大點,而不是隻過濾偶數。其次,在判斷數字是否為素數時,我們也只需要判斷該數字能否被 6 兩側的數字整除即可。(不少解法只過濾偶數,這樣以 6 個數為單位,需要判斷剩下的 3 個,而該方法只需要判斷剩下的 2 個,效率提升 30% 左右)

注意: 你也可以選擇只過濾偶數,同樣可以在指定時間內完成。雖然會慢些,但是程式碼邏輯會簡單不少。

其餘優化

  1. 先使用上述兩條數學規律過濾掉大量情況,這樣我們需要實際計算的數字就少很多很多了。
  2. 其次對於滿足上述兩種條件的數字,我們先判斷它是否是迴文數,因為判斷是迴文數的效率會比判斷是素數的效率高很多。
  3. 由於 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;
    }
}

專案地址

原題地址