【2022 力扣春招季--刷题 PK】204. 计数质数(中等)

308 阅读1分钟

“Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。”

一、题目描述

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

示例 1:

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

示例 2:

输入:n = 0
输出:0

示例 3:

输入:n = 1
输出:0

提示:

  • 0 <= n <= 5 * 106

二、思路分析

  1. 常规枚举,也就是暴力算法:

所谓质数,是指 一个大于1的自然数,除了1和它自身外,不能被其他自然数整除 ,那么就可以在遍历到当前元素的时候,判断其能不能被它之前所有的自然数整除,这样一来就需要双重 for 循环,解法如下:

int countPrimes(int n) {
    int count = 0;
    for(int i = 2; i < n; i++) {
        bool sign = true;
        for(int j = 2; j < i; j++) {
            if(i % j == 0) {
                sign = false;
                break;
            }
        }
        if(sign)
            count++;
    }
    return count;
}
  1. 暴力算法优化
    细究暴力计算的方法,我们可以发现,假如一个数为 9 ,那么其二分之一(4.5)后的数都可以不用进行计算,因为后面的数乘上倍数之后肯定大于 9 ,也就说 9 除以这些数肯定有余,因此也就不用计算了。
    事实上这个范围还可以再缩小:考虑到如果 yx 的因数,那么 x/y 也必然是 x 的因数,因此我们只要校验 y 或者 x/y 即可。而如果我们每次选择校验两者中的较小数,则不难发现较小数一定落在 [2, √x] 的区间中,因此我们只需要枚举 [2, √x] 中的所有数即可。并且,我们可以发现,一切非 2 偶数一定不可能为质数。所以,我们可以在此处进行另一步的优化。
int countPrimes(int n) {
    if(n < 3) return 0;
    //从3开始验算,所以初始值为1(2为质数)。
    int count = 1;
    for(int i = 3; i < n; i++) {
        // 当某个数为 2 的 n 次方时(n为自然数),其 & (n - 1) 所得值将等价于取余运算所得值
        // 如果 x = 2^n ,则 x & (n - 1) == x % n
        if((i & 1) == 0)   // 等价于 if(i % 2 == 0)
            continue;
        bool sign = true;
        //用 j * j <= i 代替 j <= √i 会更好。
        //因为我们已经排除了所有偶数,所以每次循环加二将规避偶数会减少循环次数
        for(int j = 3; j * j <= i; j += 2) {
            if(i % j == 0) {
                sign = false;
                break;
            }
        }
        if(sign)
            count++;
    }
    return count;
}
  1. 厄拉多塞筛法,简称埃氏筛 基本思路:将2~n中所有质数的倍数都删掉,剩余的数据就是质数。

说概念比较抽象,直接上栗子:
类比 164 的质数的筛选过程:从 2 开始遍历,把 2 的倍数都标记为 false(表示不是质数),再从 2 的下一位质数 3 (未被标记为 false 的元素)开始也同样标记其倍数。以此类推,最后仍为 true 的则为质数。

  • ①新建一个大小为 64 的数组,初始值标记为 true
  • ②从 2 开始遍历。如果布尔值为 false ,说明不为质数;如果为 true,则为质数,计算质数数量的值 +1
  • ③如果 xx 是质数,那么大于 xx 的 xx 的倍数 2x,3x,… 一定不是质数,那么将当前值的所有倍数标记为 false 。继续循环找下一个质数(即布尔值为 true 的元素),继续标记非质数...

还有一种算法:线性筛也可以解决这个问题,但面试不作考察,这里就不深究了,感兴趣的读者可以去搜些文章看看。

三、AC 代码

埃氏筛:

int countPrimes(int n) {
    int count = 0;
    //初始默认所有数为质数
    vector<bool> sign(n, true);
    for (int i = 2; i < n; i++) {
        if (sign[i]) {
            count++;
            for (int j = i + i; j < n; j += i) {
                //排除不是质数的数
                sign[j] = false;
            }
        }
    }
    return count;
}