204. 计数质数
题目来源
题目分析
给定一个整数
n
,返回小于n
的所有质数的数量。
题目难度
- 难度:中等
题目标签
- 标签:数学、筛选法
题目限制
0 <= n <= 5 * 10^6
解题思路
思路:线性筛法
-
问题定义:
- 要求统计小于
n
的所有质数的数量。可以通过筛选法高效地找出质数。
- 要求统计小于
-
核心算法:
- 使用线性筛法来筛选质数。相比于传统的埃拉托色尼筛法,线性筛法能够保证每个数只会被其最小质因数筛除一次,降低了时间复杂度。
-
关键步骤:
- 初始化一个数组
spf
(Smallest Prime Factor,最小质因数),用于存储每个数的最小质因数。(也可以使用boolean
数组isPrime
记录哪个数是素数,但是最后还要再遍历一次数组计数) - 对于每个数
i
,如果spf[i]
等于i
,说明i
是一个质数,将其加入质数列表。 - 用当前质数去筛除后续的数,更新它们的
spf
值。
- 初始化一个数组
核心算法步骤
-
初始化数组:
- 初始化数组
spf
,将每个数的最小质因数设为自身。
- 初始化数组
-
遍历筛选:
- 从
2
开始遍历到n
,如果i
是质数(即spf[i] == i
),则将i
加入质数列表。 - 用
i
乘以质数列表中的每个质数进行筛选,更新spf
。
- 从
-
返回结果:
- 最终返回质数列表的大小,即为小于
n
的质数数量。
- 最终返回质数列表的大小,即为小于
代码实现
以下是线性筛法的 Java 代码实现:
/**
* 204. 计数质数
* @param n 小于 n 的数
* @return 质数的数量
* @apiNote 线性筛法,时间复杂度O(n),空间复杂度O(n)
*/
public static int linearSieveOfEratosthenes(int n) {
int[] spf = new int[n + 1]; // 最小质因数数组
List<Integer> primes = new ArrayList<>();
// 初始化每个数的最小质因数为自身
for (int i = 2; i <= n; i++) {
spf[i] = i;
}
// 遍历每个数,执行筛选
for (int i = 2; i <= n; i++) {
// 如果 i 是质数,则加入质数列表
if (spf[i] == i) {
primes.add(i);
}
// 筛选后续的数,更新它们的最小质因数
for (Integer prime : primes) {
if (i * prime > n) {
break;
}
spf[i * prime] = prime;
if (i % prime == 0) {
break;
}
}
}
return primes.size();
}
代码解读
- 线性筛法:通过最小质因数数组
spf
来筛选质数。每个数只会被其最小质因数筛除一次,确保算法在时间复杂度上的高效性。 - 质数列表:用于存储所有筛选出的质数,最终通过质数列表的大小来得出质数的数量。
性能分析
- 时间复杂度:
O(n)
,每个数只会被其最小质因数筛除一次,因此总的复杂度为线性。 - 空间复杂度:
O(n)
,需要O(n)
的空间来存储最小质因数数组spf
。
测试用例
你可以使用以下测试用例来验证代码的正确性:
// 测试用例1
int n1 = 10;
System.out.println(linearSieveOfEratosthenes(n1)); // 输出: 4 (2, 3, 5, 7)
// 测试用例2
int n2 = 0;
System.out.println(linearSieveOfEratosthenes(n2)); // 输出: 0
// 测试用例3
int n3 = 1;
System.out.println(linearSieveOfEratosthenes(n3)); // 输出: 0
// 测试用例4
int n4 = 100;
System.out.println(linearSieveOfEratosthenes(n4)); // 输出: 25
扩展讨论
其他实现
- 埃拉托色尼筛法:传统的埃拉托色尼筛法虽然简单,但时间复杂度为
O(n log log n)
,在大数据量时性能不如线性筛法。 - 分段筛法:适用于超大范围的质数筛选,将筛选范围分为多段处理。
总结
这道题目考察了质数筛选的算法。通过线性筛法可以高效地计算小于 n
的质数数量,适用于大范围的质数筛选问题。