统计所有小于非负整数 n
的质数的数量。
示例 1:
输入:n = 10 输出:4 解释:小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。
示例 2:
输入:n = 1 输出:0
提示:
0 <= n <= 5 * 10^6
方法:
(1)平方根枚举,超时
(2)埃氏筛
枚举没有考虑到数与数的关联性,因此难以再继续优化时间复杂度。接下来我们介绍一个常见的算法,该算法由希腊数学家厄拉多塞(EratosthenesEratosthenes)提出,称为厄拉多塞筛法,简称埃氏筛。
我们考虑这样一个事实:如果 x 是质数,那么大于 x 的 x 的倍数 2x,3x,… 一定不是质数,因此我们可以从这里入手。
我们设 isPrime[i] 表示数 i 是不是质数,如果是质数则为 1,否则为 0。从小到大遍历每个数,如果这个数为质数,则将其所有的倍数都标记为合数(除了该质数本身),即 0,这样在运行结束的时候我们即能知道质数的个数。
当然这里还可以继续优化,对于一个质数 x,如果按上文说的我们从 2x 开始标记其实是冗余的,应该直接从 x⋅x 开始标记,因为 2x,3x,… 这些数一定在 x 之前就被其他数的倍数标记过了,例如 2 的所有倍数,3 的所有倍数等。
代码:
// 204. 计数质数
static void test_204() {
//int n = 1;
//int n = 10;
int n = 5000000;
int ret = countPrimes(n);
System.out.println("ret:" + ret);
}
//n = 5000000时超时
/*
static int countPrimes(int n) {
int ans = 0;
for (int i = 0; i < n; i++) {
if (isPrime(i)) {
//System.out.println("Prime i:" + i);
ans++;
}
}
return ans;
}
static boolean isPrime(int n) {
if (n == 0 || n == 1) {
return false;
}
double x = Math.sqrt(n);
for (int i = 2; i <= (int)x; i++) {
if (n%i == 0) {
return false;
}
}
return true;
}
*/
static int countPrimes(int n) {
int ans = 0;
int[] isPrime = new int[n];
Arrays.fill(isPrime, 1);
// 0,1不是质数直接排除
for (int i = 2; i < n; i++) {
if (isPrime[i] == 1) {
ans++;
// 质数的倍数排除掉
/*
* (long)i * i
* 将i的类型转换为long类型,之后相乘是long类型相乘
* (long)(i * i)
* 将i*i的结果转换为long类型,之后相乘是int类型相乘,此时可能已经发生溢出
* */
if ((long)i * i < n) {
for (int j = i * i;j < n; j+=i) {
isPrime[j] = 0;
}
}
}
}
return ans;
}