[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.

click to show more hints.

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就是这个道理。

原文地址:https://www.cnblogs.com/easonliu/p/4461701.html