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

  • 相关阅读:
    mac 卸载 node并重新安装
    最小的Django应用
    Python如何实现文本转语音
    Python语言库pyttsx3
    大数据资料
    剑指offer(29)最小的K个数
    剑指offer(28)数组中出现次数超过一半的数
    剑指offer(27)字符串的排列
    剑指offer(26)二叉搜索树与双向链表
    JS、JAVA刷题和C刷题的一个很重要的区别
  • 原文地址:https://www.cnblogs.com/easonliu/p/4461701.html
Copyright © 2020-2023  润新知