LeetCode Notes_#204_計數質數
LeetCode Notes_#204_計數質數
LeetCodeContents
題目
解答
方法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也並不是最優的,看評論區當中還有一些其他的“篩”法,效果更優。