一、内容
给定整数 n ,返回 所有小于非负整数 n 的质数的数量 。

 

示例 1:

输入:n = 10
输出:4
解释:小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。

示例 2:

输入:n = 0
输出:0

示例 3:

输入:n = 1
输出:0

 

提示:

    0 <= n <= 5 * 106
二、思路
  • 埃式筛法:一个数 p p p若是一个合数,那么必然可以被 [ 2 , p − 1 ] [2,p - 1] [2p1]中的某个质数整除,那么我们直接用前面的质数的倍数进行筛除便可以得到所有的质数。n个数中质数的个数大概为 n / l o g n n/logn n/logn, 而需要枚举的倍数次数为 2 / n , 3 / n . . . . . n / n 2/n, 3/n.....n/n 2/n,3/n.....n/n是一个调和级数,当n趋于无穷时可以认为等于 l o n n lonn lonn, 因此时间复杂度可以认为为 O ( n ) O(n) O(n),但实际上是 O ( n l o g l o g n ) O(nloglogn) O(nloglogn)
  • 线性筛法: 每个合数只被最小质因子筛一次,因此为 O ( n ) O(n) O(n)。 我们用prime记录所有的质数,然后每次枚举质数来进行筛除,分为两种情况。1.当i % prime[j] != 0时,代表prime[j]不是i的最小质因子,但是是i * prime[j]的最小质因子(因为是从小到大枚举的质数),筛除掉i * prime[j] 2.当i % prime[j] == 0时,prime[j]不仅是i的最小质因子,也是i * prime[j]的最小质因子,因此循环停止。因为后面的质数 * i 都会被最小质因子晒除,不用再重复筛。
三、代码
  • 埃式筛法
class Solution {
public:
    int countPrimes(int n) {
        int ans = 0;
        vector<bool> vis(n + 1, true); //判断某个位置是否为质数,true代表是
        vis[0] = vis[1] = false;
        for (int i = 2; i < n; i++) {
            if (vis[i]) {
               for (int j = i + i; j <= n; j += i) { //只是用质数来进行筛除
                   vis[j] = false;
               }    
            }
        }       
        for (int i = 2; i < n; i++)
            if (vis[i]) ans++;          
        return ans;                                                                                             
    }
};
  • 线性筛法
class Solution {
public:
    int countPrimes(int n) {
        int ans = 0;
        vector<bool> vis(n + 1, true); //判断某个位置是否为质数,true代表是
        vector<int> prime;
        for (int i = 2; i < n; i++) {
            if (vis[i]) prime.push_back(i); //是质数
            for (int j = 0; prime[j] <= n / i; j++) {
                vis[i * prime[j]] = false; //利用最小质因子进行筛除
                if (i % prime[j] == 0) break; //代表prime[j]是i的最小质因子,那么后面的质数就不用再枚举
            }
        }          
        return prime.size();                                                                                             
    }
};