204. 计数质数
问题描述
统计所有小于非负整数 n
的质数的数量。
示例:
示例 1:
输入:n = 10
输出:4
解释:小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。
示例 2:
输入:n = 0
输出:0
示例 3:
输入:n = 1
输出:0
算法思路
埃拉托斯特尼筛法:
- 创建布尔数组
isPrime
标记每个数是否为质数(初始全为true
) - 标记 0 和 1 为非质数
- 从 2 开始遍历到 n\sqrt{n}n:
- 若当前数
i
是质数,则将其所有倍数标记为非质数
- 若当前数
- 统计数组中值为
true
的元素数量
优化:
- 从 i2i^2i2 开始标记(更小的倍数已被标记)
- 步长取
i
(跳过已标记的合数) - 外层循环只需遍历到 n\sqrt{n}n
代码实现
class Solution {
public int countPrimes(int n) {
if (n <= 2) return 0; // 小于2时无质数
boolean[] isPrime = new boolean[n];
// 初始化数组(除0和1外全标记为质数)
for (int i = 2; i < n; i++) {
isPrime[i] = true;
}
// 埃氏筛核心:标记合数
for (int i = 2; i * i < n; i++) { // 只需遍历到 sqrt(n)
if (isPrime[i]) {
// 从 i*i 开始,以 i 为步长标记合数
for (int j = i * i; j < n; j += i) {
isPrime[j] = false;
}
}
}
// 统计质数数量
int count = 0;
for (int i = 2; i < n; i++) {
if (isPrime[i]) count++;
}
return count;
}
}
算法分析
- 时间复杂度:O(nloglogn)O(n \log \log n)O(nloglogn)
内层循环标记次数为 n2+n3+n5+⋯\frac{n}{2} + \frac{n}{3} + \frac{n}{5} + \cdots2n+3n+5n+⋯,数学证明为 O(nloglogn)O(n \log \log n)O(nloglogn) - 空间复杂度:O(n)O(n)O(n)
需要长度为n
的布尔数组
算法过程
n=20
:
-
初始化数组:
[0:false, 1:false, 2:true, 3:true, ..., 19:true]
-
标记过程:
i=2
(质数):标记 4,6,8,10,12,14,16,18i=3
(质数):标记 9,15(15>3²=9)i=4
(合数,跳过)i=5
(5²=25>20,终止循环)
-
剩余质数:
2, 3, 5, 7, 11, 13, 17, 19 → 共8个
测试用例
public static void main(String[] args) {
Solution solution = new Solution();
// 测试用例1:n=10
System.out.println("n=10: " + solution.countPrimes(10)); // 4
// 测试用例2:n=2(边界值)
System.out.println("n=2: " + solution.countPrimes(2)); // 0
// 测试用例3:n=1
System.out.println("n=1: " + solution.countPrimes(1)); // 0
// 测试用例4:n=100
System.out.println("n=100: " + solution.countPrimes(100)); // 25
// 测试用例5:n=10000
System.out.println("n=10000: " + solution.countPrimes(10000)); // 1229
// 测试用例6:n=0
System.out.println("n=0: " + solution.countPrimes(0)); // 0
}
关键点
-
从 i2i^2i2 开始标记:
对于质数i
,比 i2i^2i2 小的倍数(如 i×2i \times 2i×2、i×3i \times 3i×3)已被更小的质数标记过。 -
遍历到 n\sqrt{n}n 终止:
若n
有大于 n\sqrt{n}n 的因子,必存在小于 n\sqrt{n}n 的对应因子。 -
布尔数组优化:
使用boolean[]
而非BitSet
,在数据规模中等时效率更高。
常见问题
-
为什么外层循环到 n\sqrt{n}n?
若存在大于 n\sqrt{n}n 的因子d
使n = d × k
,则k
必小于 n\sqrt{n}n,因此该合数已被标记。 -
如何处理大整数?
当n
接近 10610^6106 时,埃氏筛仍可在毫秒级完成(时间复杂度 O(nloglogn)O(n \log \log n)O(nloglogn))。 -
能否进一步优化空间?
可使用BitSet
减少内存占用(约节省 8 倍空间),但访问速度略慢于布尔数组。