LeetCode 204. Count Primes
注:质数从2开始,2、3……
改进过程:
一、常规思路是对小于n的每一个数进行isPrime判断,isPrime(int x)函数中for(int i = 2; i <= x /2; ++i),如果x%i==0,return false。
二、将isPrime(int x)中i的范围改为i * i <= x。不用i <= sqrt(x),因为sqrt耗时。
三、isPrime中把是2、3、5倍数的数直接return,不进入for循环。
四、对于每一个数p,p2、p2+p、p2+2p……均不为素数,有点像动态规划的思想。此时可以不用isPrime(int x)函数,而是维护一个isPrime[n]的bool数组,先设里面所有的值均为true,然后i从3开始循环(对应的是如果输入n<3,直接return 0)。因为会把i2之后的一系列数在isPrime数组中的值变为false,因此i循环至i * i < n。
关于if(!isPrime[i]) continue;这一句解释一下。如果isPrime[i] == false,那么也就是说在之前的i循环里被标记过了。设之前的循环的数为p,那么 i = p2 + np,如果没有continue这一句的话,那么 j = i * i,设j也等于p2 + mp,于是得到等式 (p2 + np)2 = p2 + mp,解得m = p((p + n)2 - 1),也就是说j之前在p循环时已经被标记过了,因此可以直接continue,节省时间。以下是代码,也是题目提示中给出的最优代码。
1 public int countPrimes(int n) { 2 boolean[] isPrime = new boolean[n]; 3 for (int i = 2; i < n; i++) { 4 isPrime[i] = true; 5 } 6 // Loop's ending condition is i * i < n instead of i < sqrt(n) 7 // to avoid repeatedly calling an expensive function sqrt(). 8 for (int i = 2; i * i < n; i++) { 9 if (!isPrime[i]) continue; 10 for (int j = i * i; j < n; j += i) { 11 isPrime[j] = false; 12 } 13 } 14 int count = 0; 15 for (int i = 2; i < n; i++) { 16 if (isPrime[i]) count++; 17 } 18 return count; 19 }
五、在最后计算cnt时,从2开始到n-1都被判断了一遍,但是偶数绝对不是素数,因此是没必要访问其在数组isPrime中的值的。
既然计算cnt时可以跳过偶数,那么在之前的标记isPrime的for循环里,也可以跳过偶数,相应的j每次+= 2 * i,因为p(p + 1)必定为偶数。
所以for循环里依次变为i += 2, j += 2 * i, i += 2(上一步为++i, j += i, ++i)。
最终版代码
1 class Solution { 2 public: 3 int countPrimes(int n) { 4 if(n < 3) return 0; 5 bool* isPrime = new bool [n]; 6 for(int i = 3; i < n; ++i) 7 isPrime[i] = true; 8 9 for(int i = 3; i * i <= n; i += 2){ 10 if(!isPrime[i]) continue; 11 for(int j = i * i; j < n; j += 2 * i) 12 isPrime[j] = false; 13 } 14 15 int cnt = 1; 16 cout<<isPrime[9]<<endl; 17 for(int i = 3; i < n; i += 2){ 18 if(isPrime[i]) ++cnt; 19 } 20 return cnt; 21 } 22 };