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

你可能感兴趣的:(LeetCode)