线性筛(通常被称为欧拉筛)的核心目的只有一个:把寻找质数的时间复杂度压缩到终极的 O ( n ) O(n) O(n)。它的绝招在于"不走冤枉路"。
传统的埃氏筛法会重复标记同一个合数(比如 12 既被 2 筛除,又被 3 筛除),而线性筛通过极其巧妙的规则避免了这种浪费。它的原理可以浓缩为以下 3 个核心点:
- 唯一击杀原则 :确保每一个合数,只被它自己的"最小质因数"筛掉一次。
- 边找边筛 :从 2 开始往后遍历数字 i i i。如果 i i i 没被别人标记过,那它就是质数,直接收入质数表。接着,用 i i i 依次去乘以目前已经找到的质数 p p p,把它们的乘积 i × p i \times p i×p 标记为合数。
- 灵魂刹车(最核心逻辑) :在内层乘法循环里,如果发现 i m o d p = = 0 i \bmod p == 0 imodp==0,必须立刻打断循环 !因为这说明 i i i 里面已经包含了 p p p 这个因子。如果继续拿 i i i 去乘后面更大的质数,算出来的结果以后肯定会被当前的 p p p(也就是更小的质因数)提前筛掉,现在就不必多此一举了。
完整代码如下:
cpp
class Solution {
public:
int countPrimes(int n) {
int idx = 0;
vector<bool> st(n, false);
vector<int> primes(n);
for(int i = 2; i < n; i ++ ) {
if(!st[i]) primes[idx ++ ] = i;
for(int j = 0; j < idx && primes[j] * i < n; j ++ ) {
st[i * primes[j]] = true;
if(i % primes[j] == 0) break;
}
}
return idx;
}
};
注意,当 primes[j] * i 可能溢出时,我们不能将筛素数的判断写成:
cpp
for(int j = 0; j < idx && primes[j] < n / i; j ++ )
因为 n / i 是下取整,这会导致我们漏掉部分解。
直接用 (long long) 转换就行了。