题目描述:给定整数 n
,返回 所有小于非负整数 n
的质数的数量。
示例 1:
输入:n = 10
输出:4
解释:小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。
示例 2:
输入:n = 0
输出:0
示例 3:
输入:n = 1
输出:0
所谓质数也叫做素数,即除了1没有其他的可以整除的自然数。所以我们可以想到遍历得到答案。
class Solution {
public:
int countPrimes(int n) {
int ans = 0; //记录质数的个数
for(int i = 2;i <= n;i++){
int j = 2;
for(;j < i;j++){
if(i % j == 0){
break;
}
}
if(j == i){
ans++; // 若比i小的自然数没有可以被整除的就表明为质数。
}
}
return ans;
}
};
但这个方法时间复杂度较高,为O(n²);我们需要对其进行优化,比如,可以简单举一个例子,如果n = 64 的话,在我们判断是否有可以整除的自然数的时候,我们不用遍历到n,我们遍历到即可,即遍历到8即可。原因是,大于8之后,如果i可以被64整除的话,那个64/i的结果肯定是小于8的一个自然数,那么我们肯定会在8之前就可以遍历到。即我们对于一个自然数n是否为质数,我们遍历到
即可。故我们可以把代码修改成以下情况:
class Solution {
public:
int countPrimes(int n) {
int ans = 0; //记录质数的个数
for(int i = 2;i <= n;i++){
int j = 2;
for(;j * j <= i;j++){
if(i % j == 0){
break;
}
}
if(j * j > i){
ans++; // 若比i小的自然数没有可以被整除的就表明为质数。
}
}
return ans;
}
};
即使这样,时间复杂度为O(n),提交仍然超时。经过学习,我们学习到了一种新算法——埃氏筛。这个算法的主要思想是,从小到大进行遍历,每次遍历的时候,对其倍数进行标记,然后根据它是否标记来来判断是否需要进行遍历,减小遍历的次数。例如我们遍历2,对2的倍数进行标记即对4,6,8进行标记。到4的时候就不需要遍历,因为4的倍数也是2的倍数无需重复遍历,即减少了遍历次数。具体看以下代码:
class Solution {
public:
int countPrimes(int n) {
int ans = 0; //记录质数的个数
vector<int>sign(n + 1,1);//标记数组
for(int i = 2;i <= n;i++){
if(sign[i] == 1){ //对没有标记的元素进行遍历
for(int j = i + i;j <= n;j += i){
sign[j] = 0;//因为j是i的倍数,故j必不是质数
}
}
}
for(int i = 2;i < n;i++){ //对没有标记的元素进行统计
if(sign[i] == 1){
ans++;
}
}
return ans;
}
};
这个方法的时间复杂度为O(nloglogn),可通过。我认为以上方法是新手小白可以接受的方法。当然还有其他更好的方法,如线性筛等。作者也是新手小白,故不做赘述。