题目链接
解题思路
-
质数是只能被
1
和自身
整除的数 -
本文的解题思路对应着题目给出的显性提示一步步优化
-
暴力枚举法
- 从
[2,n)
枚举数,如果其能被[2, n - 1]
中任意数整除,那么不是质数
- 从
-
优化版暴力枚举法
- 我们可以发现,假如一个数为10,那么其二分之一(4.5)后的数都可以不用进行计算,因为肯定是有余的。
- 事实上情况会比这更好一些:对正整数
n
,如果用[2,sqrt(n)]
之间的所有整数去除,均无法整除,则n
为质数
-
埃氏筛
-
枚举没有考虑到数与数的关联性,因此难以再继续优化时间复杂度。接下来我们介绍一个常见的算法,该算法由希腊数学家厄拉多塞提出,称为厄拉多塞筛法,简称埃氏筛。
-
我们考虑这样一个事实:如果
x
是质数,那么大于x
的x
的倍数2x
,3x
,… 一定不是质数,因此我们可以从这里入手。我们设isPrime[i]
表示数i
是不是质数,如果是质数则为1
,否则为0
。从小到大遍历每个数,如果这个数为质数,则将其所有的倍数都标记为合数(除了该质数本身),即0
,这样在运行结束的时候我们即能知道质数的个数。 -
通俗一点来说就是首先初始化长度为
n
的标记数组,表示这个数组是否为质数。数组初始化所有的数都是质数 -
从
2
开始将当前数字的倍数全都标记为合数。标记到 n \sqrt{n} n时停止即可。具体可以看来自维基百科的动画。
-
AC代码
//暴力枚举法,超时
class Solution {
public int countPrimes(int n) {
int ans = 0;
for (int i = 2; i < n; i++) {
ans += isPrime(i) ? 1 : 0;
}
return ans;
}
public static boolean isPrime(int n) {
for (int i = 2; i < n; i++) {
if (n % i == 0)
return false;
}
return true;
}
}
//优化暴力枚举法,缩小比较范围,由n变成sqrt(n)
class Solution {
public int countPrimes(int n) {
int ans = 0;
for (int i = 2; i < n; i++) {
ans += isPrime(i) ? 1 : 0;
}
return ans;
}
public static boolean isPrime(int n) {
for (int i = 2; i * i <= n; i++) {
if (n % i == 0)
return false;
}
return true;
}
}
//埃氏筛法
class Solution {
public int countPrimes(int n) {
boolean[] isPrime = new boolean[n];
Arrays.fill(isPrime, true);
for (int i = 2; i * i < n; i++) {
if (isPrime[i]) {
for (int j = i * i; j < n; j += i) {
isPrime[j] = false;
}
}
}
int ans = 0;
for (int i = 2; i < n; i++) {
if (isPrime[i])
ans++;
}
return ans;
}
}