[Leetcode] Count Primes
Description:
Count the number of prime numbers less than a non-negative number, n
Hint: The number n could be in the order of 100,000 to 5,000,000.
Credits:
Special thanks to @mithmatt for adding this problem and creating all test cases.
厄拉多塞筛法。从第一个素数开始把它的倍数去掉,那么下一个没有被去掉的数一定是素数,重复上面的过程。具体可以看下图。
但是提交却发现超时了!超时了!!!!难道这不是正解?所以经过一顿优化,终于AC掉了。
1 class Solution { 2 public: 3 int countPrimes(int n) { 4 vector<bool> p(n, true); 5 //去掉2以外的所有偶数 6 for (int i = 4; i < n; i += 2) p[i] = false; 7 //上一步已经去掉了偶数,所以这里可以使用i += 2,j += 2 8 for (int i = 3; i * i < n; i += 2) { 9 if (p[i]) for (int j = 3; i * j < n; j += 2) { 10 p[i * j] = false; 11 } 12 } 13 int cnt = 0; 14 for (int i = 2; i < n; ++i) if (p[i]) ++cnt; 15 return cnt; 16 } 17 };
可是,居然花了890ms,可能是用了vector吧,后来又用数组试了一遍,果然:
1 class Solution { 2 public: 3 int countPrimes(int n) { 4 bool *p = new bool[n]; 5 memset(p, true, sizeof(bool) * n); 6 for (int i = 2; i * i < n; ++i) { 7 if (p[i]) for (int j = 2; i * j < n; ++j) { 8 p[i * j] = false; 9 } 10 } 11 int cnt = 0; 12 for (int i = 2; i < n; ++i) if (p[i]) ++cnt; 13 delete [] p; 14 return cnt; 15 } 16 };
没有任何优化,只花了400ms,优化过的只要170ms。所以,vector与原生数组在效率上的差距,由此题可见一斑。
上面的方法是素数筛选法,但是有一个问题,就是我们每次将当前找到的最大的素数的倍数都筛选掉,但是有很多数是这些素数的公倍数,也就是说我们在筛选的时候设置了多次,这个算法并不是线性的。下 面要说的就是线性素数筛选法,大体跟上面的方法一样,但是可以保证线性时间,下面的代码AC只要40ms。
1 class Solution { 2 public: 3 int countPrimes(int n) { 4 bool *tag = new bool[n]; 5 int *prime = new int[n]; 6 int cnt = 0; 7 memset(tag, true, sizeof(bool) * n); 8 for (int i = 2; i < n; ++i) { 9 if (tag[i]) prime[cnt++] = i; 10 for (int j = 0; j < cnt && i * prime[j] < n; ++j) { 11 tag[i * prime[j]] = false; 12 if (i % prime[j] == 0) break; 13 } 14 } 15 delete [] tag; 16 delete [] prime; 17 return cnt; 18 } 19 };
要点就在于第12行 if (i % prime[j] == 0) break;
因为合数可以由一个质数数与另一个数相乘得到,而同时假设合数 a = 质数b × 质数c × 一个数d,令 e = c × d,假设b ≥ c,e为合数,令f=d × b,a=f × c, 其中c即大的质数和该合数的乘积,可用一个更大的合数和比其小的质数相乘得到这也是if(!( i % prime[j]))break;的含义,这也是线性筛法算质数表的关键所在。
举个例子:
比如i = 9,现在素数是2 3 5 7
进入第二重循环了,tag[2 * 9] = false; tag[3 * 9] = false; 这个时候9%3==0,要跳出了,为什么不做 tag[5* 9] = false;呢?
因为5 * 9 可以用3 * 15来代替,如果这个时候你计算了,那么到i=15的时候这个数还会被重复计算一次,所以这里大量避免了重复运算,所以也就节省了时间。
这里总结一句话就是,一个大的合数和这个能除尽的质数的乘积,一定能被一个比起小的质数和合数更大的合数乘积来代替。
不懂的时候想想 5*9 = 5*3*3 = 3*15就是这个道理。