题目来源:LeetCode204:计数质数
问题抽象: 设计算法高效计算小于非负整数 n 的质数数量(统计范围 [0, n-1]),需满足以下核心需求:
-
质数定义:
- 质数需大于 1 且仅能被 1 和自身整除(如
2,3,5,7,...); - 特殊值:
0和1非质数。
- 质数需大于 1 且仅能被 1 和自身整除(如
-
高效计算策略:
- 埃拉托斯特尼筛法:
- 初始化长度
n的布尔数组isPrime(默认true); - 从
2开始遍历至√n,若isPrime[i]为true,则标记i的所有倍数为false;
- 初始化长度
- 空间优化:使用原生布尔数组(非位图)。
- 埃拉托斯特尼筛法:
-
性能约束:
- 时间复杂度 O(n log log n)(埃氏筛理论最优);
- 空间复杂度 O(n)(存储标记数组);
- 支持
n ≤ 5×10^6(需通过所有测试用例)。
-
边界处理:
n=0或n=1时输出0(无数值大于 1);n=2时输出0(小于2的质数数量为0);n=3时输出1(仅质数2);- 大数优化:
- 外层循环终止于
√n(避免冗余标记); - 内层标记从
i²开始(跳过已处理倍数)。
- 外层循环终止于
输入:整数 n(值域 [0, 5×10^6])
输出:整数(小于 n 的质数数量)
解题思路
核心算法:埃拉托斯特尼筛法(优化版)
- 特殊处理:当
n ≤ 2时,直接返回 0(因为小于 2 的质数为 0)。 - 布尔数组标记非质数:创建长度为
n的布尔数组notPrime,索引表示数字,true表示非质数。 - 预处理偶数为非质数:除了数字 2,所有偶数都不是质数。遍历所有偶数索引(≥4),标记为
true。 - 计数质数:初始化质数计数器为 1(数字 2 是质数)。
- 遍历奇数:从 3 开始,每次步进 2(只检查奇数):
- 若当前数未被标记为非质数,则计数器加 1。
- 若当前数
i不超过√n,则标记其奇数倍为非质数(从i*i开始,步长2*i,避免重复标记偶数倍)。
- 返回结果:最终计数器的值即为小于
n的质数数量。
亮点:
- 跳过偶数:外层循环只遍历奇数,减少一半迭代次数。
- 步长优化:内层标记时步长为
2*i,只标记奇数倍(偶数已预处理)。 - 提前终止:当
i > √n时,其倍数已超出范围,无需标记。
代码实现(Java)🔥点击下载源码
class Solution {
public int countPrimes(int n) {
if (n <= 2) {
return 0;
}
boolean[] notPrime = new boolean[n]; // 标记非质数
notPrime[0] = true;
notPrime[1] = true;
// 标记所有大于2的偶数为非质数
for (int i = 4; i < n; i += 2) {
notPrime[i] = true;
}
int count = 1; // 数字2是质数
int sqrtN = (int) Math.sqrt(n);
// 遍历奇数:3,5,7,...,n-1
for (int i = 3; i < n; i += 2) {
if (!notPrime[i]) { // i是质数
count++;
if (i > sqrtN) { // 超过√n时,倍数已超出范围
continue;
}
// 标记i的奇数倍(i*i, i*i+2i, ...)为非质数
for (long j = (long) i * i; j < n; j += 2 * i) {
notPrime[(int) j] = true;
}
}
}
return count;
}
}
代码说明
- 初始化处理:
n ≤ 2时直接返回 0。- 布尔数组
notPrime标记非质数,默认false(质数)。 - 显式标记 0、1 为非质数。
- 预处理偶数:
- 循环标记所有大于 2 的偶数(4,6,8,…)为非质数。
- 计数与标记:
- 初始化计数器
count = 1(包含质数 2)。 - 遍历奇数(3 到 n-1):
- 若
i是质数(notPrime[i] = false),则count++。 - 当
i ≤ √n时,从i*i开始,以步长2*i标记非质数(避免偶数倍)。
- 若
- 初始化计数器
- 返回结果:计数器
count即为小于n的质数数量。
提交详情

1393

被折叠的 条评论
为什么被折叠?



