LeetCode 204. 计数质数
题目描述

题意分析
统计非负整数
n之前有多少个质数,范围是严格小于n的所有整数,不包含n本身。质数必须大于1,并且除了1和自身没有其他正因数,因此真正需要统计的范围是[2, n)。返回的是数量,不需要返回质数列表。
n <= 2时这个范围为空,答案为0;题目上界较大,逐个数字反复试除会做大量重复工作。
解法:埃氏筛
核心思路
[!blue]
筛法把判断方向反过来:不逐个询问一个数能否被整除,而是从小到大利用已经发现的质数,统一标记它们的倍数为合数。用
composite[x]表示数字x是否已被确认是合数,初始全部为false。扫描到
i时,如果它尚未被标记,就可以把它当作质数。因为一个合数一定含有比自身更小的质因子,处理那个质因子时就会标记它。若i已经是合数,它的倍数也能由这些较小质因子负责,不需要重复作为筛选源。对质数
i,从i * i开始标记,每次增加i。比平方更小的非自身倍数可写为i * k,其中2 <= k < i;k至少含有一个比i小的质因子,这个倍数已经被前面的筛选处理。因此跳过这些较小倍数不会漏标,也不会把质数i自己划掉。外层只需处理到平方仍小于
n的位置。任意合数都可以分成两个大于一的因子,其中至少一个不超过它的平方根,所以一定会被这个范围内的某个质因子筛掉。代码用i <= (n - 1) / i表示i * i < n,先判断合法范围再计算平方。筛选结束后,在
[2, n)中统计所有仍未标记的位置即可。零和一虽然同样保持默认值,却不在统计范围内,不会被当成质数。
解题步骤
- 若
n <= 2,直接返回0;否则创建长度为n的布尔数组composite。- 从
i = 2开始,只要i <= (n - 1) / i就继续;已标记为合数的i跳过。- 对未标记的
i,从i * i起,每次增加i,将所有小于n的对应位置设为true。- 遍历下标
2到n - 1,统计值为false的位置并返回数量。
代码实现
class Solution {
public int countPrimes(int n) {
if (n <= 2) {
return 0;
}
boolean[] composite = new boolean[n];
for (int i = 2; i <= (n - 1) / i; i++) {
if (composite[i]) {
continue;
}
// 更小倍数已被较小质因子标记,从平方开始避免重复工作。
for (int multiple = i * i; multiple < n; multiple += i) {
composite[multiple] = true;
}
}
int count = 0;
for (int i = 2; i < n; i++) {
if (!composite[i]) {
count++;
}
}
return count;
}
}
func countPrimes(n int) int {
if n <= 2 {
return 0
}
composite := make([]bool, n)
for i := 2; i <= (n-1)/i; i++ {
if composite[i] {
continue
}
// 更小倍数已被较小质因子标记,从平方开始避免重复工作。
for multiple := i * i; multiple < n; multiple += i {
composite[multiple] = true
}
}
count := 0
for i := 2; i < n; i++ {
if !composite[i] {
count++
}
}
return count
}
复杂度分析
- 时间复杂度:$O(n\log\log n)$。只有质数作为标记源,质数
p至多标记约n / p个倍数,这些工作量之和为 $O(n\log\log n)$;最后统计还需 $O(n)$。- 空间复杂度:$O(n)$,布尔数组记录小于
n的数是否为合数。
关键点总结
[!green]
- 从已知质数批量排除合数,避免对每个候选数重复试除。
- 从平方开始,省去已经由更小质因子处理的倍数。
- 每个合数都有不超过其平方根的质因子,保证提前结束外层筛选仍能找全合数。
- 筛选和统计都使用严格小于
n的右边界,零和一单独排除在统计范围外。
易错点总结
[!yellow]
- 把零或一也纳入未标记位置的统计,会把非质数算进答案。
- 使用包含
n的循环边界,会违背严格小于的要求,并可能访问长度为n的数组末尾之外。- 从质数本身开始标记,会把真正的质数也筛掉;从平方开始才符合本实现的筛选范围。
- 内层步长写成一,会把非倍数位置一起标记;步长必须等于当前质数。
- 把未标记值直接解释成质数,却没有先完成必要的小质因子筛选,会错误保留合数。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 补充题 153. 整数的质因数分解 | 简单 | 试除分解单个数与筛出范围内全部素数任务不同;可先筛小素数,再用于多个数的试除。 |
| 补充题 214. 不大于 n 的全部素数 | 中等 | 都用筛法标记合数;本题统计质数个数,补充题输出质数列表。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!