题目描述:
Count the number of prime numbers less than a non-negative number, n.
要完成的函数:
int countPrimes(int n)
说明:
1、题目看上去非常简单和熟悉。给定一个非负数n,要求返回小于n的所有素数的个数。
2、处理一下边界条件,n<=2时返回0,n=3时返回1,n=4时返回2。
3、传统方法:
对于小于n的每个数i,判断i是不是素数。判断方法是对于每个大于等于2且小于等于i/2的数,确定i能否整除这个数。
双重循环,暴力解法。然后在这道题中超时了。
代码如下:
bool prime(int i)
{
for(int j=2;j<=i/2;j++)//小循环
{
if(i%j==0)
return false;
}
return true;
}
int countPrimes(int n) {
int count=0;
if(n<=2)
return count;
else if(n==3)
return 1;
else if(n==4)
return 2;
count=2;
for(int i=4;i<n;i++)//大循环
{
if(prime(i))
count++;
}
return count;
}
4、改进1:
我们尝试做一些改进,比如在大循环中,能不能只判断奇数,毕竟偶数都不是素数,没有必要判断,这样可以省很多时间。
此外,在小循环中,能不能控制j为奇数,毕竟大循环中的要判断的奇数,只会由另外两个奇数相乘而得到。
还有,我们可以把小循环中的判断条件:i/2,改成sqrt(i)。这个也能省不少时间。
我们来试一下:
bool prime(int i)
{
for(int j=3;j<=sqrt(i);j+=2)//j每次都+2
{
if(i%j==0)
return false;
}
return true;
}
int countPrimes(int n) {
if(n<=2)//这次要判断的边界条件比较多,相信看完一整个代码的你会理解的
return 0;
else if(n==3)
return 1;
else if(n==4||n==5)
return 2;
else if(n==6||n==7)
return 3;
else if(n==8)
return 4;
int count=4;
for(int i=9;i<=n;i+=2)//从i=9开始判断,只对奇数进行判断
{
if(prime(i))
count++;
if(i==n&&prime(n))//如果n是一个奇数和素数,那么count要减去1;如果n是奇数和非素数,那么不用减
//去1,因为上一行代码在最后没有执行到。
count--; //如果n是一个偶数,那更加不用减去1。
}
return count;
}
这次代码通过测试,beats 7.36% of cpp submissions,实测452ms。
5、改进2:
我们能否再做一些改进?
上述代码浪费时间的地方在于:
比如我们要判断19*31=589是不是素数,那么我们要对589%9做出判断,对589%11做出判断,对589%13做出判断,对589%15做出判断,对589%19做出判断。
然后我们对于下一个数19*41=779判断是否素数,要对779%9做出判断,对779%11做出判断,对779%13做出判断,对779%15做出判断,对779%19做出判断。
我们浪费了很多时间在不停地判断上面。我们可不可以用素数相乘的方式,直接生成一些合数,然后不断地筛掉它们。这样可以避免花费大量时间在判断上面。
代码如下:
int countPrimes(int n)
{
if (n<=2)
return 0;
vector<bool> prime(n, false);//定义一个长度为n,初始为false的bool型vector容器
int sum = 1;
int upper = sqrt(n);
for (int i=3; i<n; i+=2) //对每个奇数进行处理
{
if (!prime[i]) //初始i=3,是一个质数,进入后续处理。之后每次都判断是不是质数,如果是就进行处理。
{
sum++;
if (i>upper)
continue;
for (int j=i*i; j<n; j+=2*i) //比如3*3=9,9就是一个合数。然后9+3*2=15=i*i+2i,也是一个合
{ //数。后续不断循环处理。这里最好是处理成j+=2*i,而不是j+=i,
//思考一下原因?评论区有答案~这个细节可以省一半时间。
prime[j] = true;
}
}
}
return sum;
}
最终实测16ms,beats 97% of cpp submissions。