1. 程式人生 > 實用技巧 >LeetCode Notes_#204_計數質數

LeetCode Notes_#204_計數質數

LeetCode Notes_#204_計數質數

LeetCode

Contents

題目


解答

參考了如何高效判定、篩選素數 - 計數質數

方法1:暴力(超時)

最容易想到的方法,當然就是暴力遍歷[2,n - 1]範圍內所有數字。
對於每一個數字i,又將其傳入輔助函式isPrime(),對[2,i - 1]的每個數字求餘,如果遇到餘數為0的情況,返回false。否則返回true

class Solution {
    public int countPrimes(int n) {
        int count = 0;
        for(int i = 2;i < n;i++){
            if(isPrime(i)) count++;
        }
        return count;
    }

    boolean isPrime(int n){
        for(int i = 2;i < n;i++){
            if(n % i == 0) return false;
        }
        return
true; } }

複雜度分析

時間複雜度:O(n2)
空間複雜度:O(1)

方法2:暴力法優化1

其實上邊的暴力解法稍加優化就可以ac(雖然速度依然慢)。
這個優化的思路就是,在isPrime()輔助方法當中,不需要遍歷到n - 1,只需要遍歷到sqrt(n)就可以了。
原因如下,以n=12為例,其實以sqrt(12)作為分界點,後邊的乘積的數字組合和前邊的乘積的數字組合剛好是顛倒的。
對於isPrime(int n)當中的n,如果在[2,sqrt(n)]範圍內沒有發現可以整除n的數字,就說明n一定是質數。

12 = 2 × 6
12 = 3 × 4
12 = sqrt(12) × sqrt(12)
12 = 4 × 3
12 = 6 × 2
class Solution {
    public int countPrimes(int n) {
        int count = 0;
        for(int i = 2;i < n;i++){
            if(isPrime(i)) count++;
        }
        return count;
    }

    boolean isPrime(int n){
        //只需要遍歷到sqrt(n)即可
        for(int i = 2;i * i <= n;i++){
            if(n % i == 0) return false;
        }
        return true;
    }
}

複雜度分析

時間複雜度:O(n* sqrt(n))
空間複雜度:O(1)

方法3:暴力法優化2

還有一種優化的方法,是排除法,具體來說就是:
質數的倍數肯定不是質數,這樣就可以把所有的非質數排除,剩下的數字就是質數。

class Solution {
    public int countPrimes(int n) {
        boolean[] isPrime = new boolean[n];
        //將整個陣列都初始化為true,之後開始“排除法”,即將非質數位置置為false
        Arrays.fill(isPrime, true);
        for(int i = 2;i < n;i++){
            if(isPrime[i]){
                //將i的所有倍數的索引位置設定為false
                for(int j = 2*i;j < n;j += i){
                    isPrime[j] = false;
                }
            }
        }
        //最後再進行一次計數即可
        int count = 0;
        for(int i = 2;i < n;i++){
            if(isPrime[i]) count++;
        }
        return count;
    }
}

複雜度分析

時間複雜度:外層迴圈是O(n),內層迴圈不太好分析...
空間複雜度:O(1)

方法4:進一步優化:Sieve of Eratosthenes(厄拉多塞篩法)

在方法3的基礎上,進一步優化的方法是,將外層迴圈的遍歷範圍修改為[2, sqrt(n)]
見如下圖解。

對於n=100的情況,外層迴圈只需要遍歷[2,sqrt(n)]即可,原因是,對於所有10以後的數字,要乘以一個數,乘積小於100,那麼必然是小於10的數。所以就是類似方法2當中,出現了乘積組合的重複。
其實只需要計算[2,sqrt(n)]範圍內數字的倍數就行了,之後的其他數字的倍數,都是[2,sqrt(n)]範圍內數字倍數的重複。

class Solution {
    public int countPrimes(int n) {
        boolean[] isPrime = new boolean[n];
        //將整個陣列都初始化為true,之後開始“排除法”,即將非質數位置置為false
        Arrays.fill(isPrime, true);
        //將這裡的範圍修改為[2, sqrt(n)]
        for(int i = 2;i * i <= n;i++){
            if(isPrime[i]){
                //將i的所有倍數的索引位置設定為false
                for(int j = 2*i;j < n;j += i){
                    isPrime[j] = false;
                }
            }
        }
        //最後再進行一次計數即可
        int count = 0;
        for(int i = 2;i < n;i++){
            if(isPrime[i]) count++;
        }
        return count;
    }
}

複雜度分析

時間複雜度:O(N * loglogN)
空間複雜度:O(1)

總結

這道題其實方法2是最容易想到的,但是其實還可以逐步優化,其實方法4也並不是最優的,看評論區當中還有一些其他的“篩”法,效果更優。