LeetCode204題:計數質數
這道題真的是解法很多,但滿足時間複雜度的還不太容易想。
解法一:暴力法
外層迴圈從2到n,內層迴圈從2到ni,然後ni取模內層迴圈的值,判斷是否為質數。複雜度O(n2),捨棄。
解法二:根據之前的質數求
思路:一個數如果與比它小的所有的質數取模後結果都不為0時,那麼此數也是質數。所以,從2開始遍歷到n時,每遇到一個質數就將其放到ArrayList中(用ArrayList而不用treeset,因為不需要多餘的排序操作)。每次判斷一個數是不是質數時,就遍歷ArrayList進行取模比較。
雖然比較的次數少了很多了,但遺憾的是依然沒通過測試用例。
public static int countPrimes(int n) { if(n < 3) return 0; if(n == 3) return 1; List<Integer> set=new ArrayList<Integer>(); set.add(2); int flag = 1; for(int i=3;i<n;i++){ Iterator<Integer> it = set.iterator(); while(it.hasNext()){ if(i%it.next()==0){ flag = -1; break; } } if(flag == 1){ set.add(i); } flag = 1; } return set.size(); }
解法三:質數折半比較
在解法二中,每次都比較了比當前值小的所有質數。但觀察後可以發現,一個數如果與比它小的所有質數的前一半取模都不為0的話,那麼就沒必要與後一半進行比較了,因為除以小的數得到的數就是大的,所有如果除小的除不盡,那麼除大的肯定除不盡。這樣又能減少比較次數。
遺憾的是,依然沒通過全部的測試用例。好像是卡在1500000了。
public static int countPrimes(int n) { if(n < 3) return 0; if(n == 3) return 1; List<Integer> set=new ArrayList<Integer>(); set.add(2); int flag = 1; for(int i=3;i<n;i++){ Iterator<Integer> it = set.iterator(); int mid = set.size(); int count = 0;//增加count變數用來折半 while(it.hasNext()&&count<=mid/2){ if(i%it.next()==0){ flag = -1; break; } count++; } if(flag == 1){ set.add(i); } flag = 1; } return set.size(); }
解法四:根號n比較
解法二中採用的比較前一半的質數的方法複雜度還是不符合要求,那麼考慮將n取根號後取整,然後再與ArrayList中小於(int)Math.sqrt(n)的質數進行取模比較,思想其實與解法二是一樣的,都是基於除以小的數會得到大的數,而一個數想要被整除並且餘數大於除數的話,除數最大就是sqrt(n),而解法二採用的是質數折半,並不如這種精確。這樣就能進一步減少比較次數。
並且,這種方法通過了所有測試用例。
public static int countPrimes(int n) { if(n < 3) return 0; if(n == 3) return 1; List<Integer> set=new ArrayList<Integer>(); set.add(2); int flag = 1; for(int i=3;i<n;i++){ Iterator<Integer> it = set.iterator(); int mid = (int)Math.sqrt(n); while(it.hasNext()){ int next = it.next(); if(next<=mid){ if(i%next==0){ flag = -1; break; } }else{ break; } } if(flag == 1){ set.add(i); } flag = 1; } return set.size(); }
解法五:埃拉托色尼素數篩法
這種演算法的思想是,先假定所有的數都是質數,並以布林型陣列存放(質數用true表示)。然後從2開始迭代,因為預設的都是質數,所以2是質數,那麼2的平方及2的平方+2m一定都不是質數,所以把對應不是質數的元素改為false。然後迭代到3,由於預設的是質數,並且之前的2的迴圈並沒有修改3為false,所以3依然是質數,那麼同理,3的平方及3的平方+3m也一定都是非質數,故修改為true。以此迭代。
下面的程式碼是摘抄的網上的。
public static int countPrimes(int n) { //暴力演算法是過不了的qwq
//這個要用篩法實現
boolean[] isPrime = new boolean[n];
int result = 0;
for (int i = 2; i < n; i++) {
isPrime[i] = true; //先初始化為true
}
for (int i = 2; i * i < n; i++) { //這一次for迴圈找出所有不是素數的數(也就是說被篩掉了)
if (!isPrime[i]) {
//既然已經被篩掉了就不用管了
continue;
}
else {
for (int j = i * i; j < n; j += i) {
//由於i現在是一個素數, 那麼i的平方一定不是素數,i^2 + i; i^2 + 2i也一定不是素數
isPrime[j] = false;
}
}
} //所有不是素數的數已經全部篩掉
//計算剩餘的素數個數
for (int i = 2; i < n; i++) {
if (isPrime[i] == true) {
result++;
}
}
return result;
}
下面是n等於100000時四個方法的用時比較(除了第一種暴力演算法):