如題:204. 計數質數
給定整數 n ,返回 所有小於非負整數 n 的質數的數量 。
示例 1:
輸入:n = 10
輸出:4
解釋:小於 10 的質數一共有 4 個, 它們是 2, 3, 5, 7 。
示例 2:輸入:n = 0
輸出:0
示例 3:輸入:n = 1
輸出:0提示:
0 <= n <= 5 * 106
方法一:
暴力枚舉:
1 class Solution { 2 public int countPrimes(int n) { 3 int ans = 0; 4 for (int i = 2; i < n; ++i) { 5 ans += isPrime(i) ? 1 : 0; 6 } 7 return ans; 8 } 9 10 public boolean isPrime(int x) { 11 for (int i = 2; i * i <= x; ++i) { 12 if (x % i == 0) { 13 return false; 14 } 15 } 16 return true; 17 } 18 }
方法二:
埃氏篩:
枚舉沒有考慮到數與數的關聯性,因此難以再繼續優化時間復雜度。接下來我們介紹一個常見的算法,該算法由希臘數學家厄拉多塞提出,稱為厄拉多塞篩法,簡稱埃氏篩。
我們考慮這樣一個事實:如果 x 是質數,那么大於 x 的 x 的倍數 2x,3x,… 一定不是質數,因此我們可以從這里入手。
我們設 isPrime[i] 表示數 i 是不是質數,如果是質數則為 1,否則為 0。從小到大遍歷每個數,如果這個數為質數,則將其所有的倍數都標記為合數(除了該質數本身),即 0,這樣在運行結束的時候我們即能知道質數的個數。
這種方法的正確性是比較顯然的:這種方法顯然不會將質數標記成合數;另一方面,當從小到大遍歷到數 x 時,倘若它是合數,則它一定是某個小於 x 的質數 y 的整數倍,故根據此方法的步驟,我們在遍歷到 y 時,就一定會在此時將 x 標記為 isPrime[x]=0。因此,這種方法也不會將合數標記為質數。
當然這里還可以繼續優化,對於一個質數 x,如果按上文說的我們從 2x 開始標記其實是冗余的,應該直接從 x⋅x ,開始標記, x⋅(x+1), x⋅(x+2)因為 2x,3x,… 這些數一定在 x 之前就被其他數的倍數標記過了,例如 2 的所有倍數,3 的所有倍數等。
1 class Solution { 2 public int countPrimes(int n) { 3 int[] isPrime = new int[n]; 4 Arrays.fill(isPrime, 1); 5 int res = 0; 6 for(int i=2; i<n; i++){ 7 if(isPrime[i]==1){ 8 res+=1; 9 if((long)i*i < n){ 10 for (int j = i*i; j<n; j+=i){ 11 isPrime[j] = 0; 12 } 13 } 14 } 15 } 16 return res; 17 } 18 }
方法三:線性篩
1 class Solution { 2 public int countPrimes(int n) { 3 List<Integer> list = new ArrayList<>(); 4 int[] isPrime = new int[n]; 5 Arrays.fill(isPrime, 1); 6 for (int i=2; i<n; i++){ 7 if(isPrime[i] == 1){ 8 list.add(i); 9 } 10 for (int j=0; j<list.size() && i*list.get(j)<n; j++){ 11 isPrime[i*list.get(j)] = 0; 12 if(i%list.get(j) == 0){ 13 break; 14 } 15 } 16 } 17 return list.size(); 18 } 19 }