题目描述:统计所有小于非负整数 n 的质数的数量。
思路:
由于素数之间不存在明确的简单关系,所以需要把所有数字取出,逐一判断是否是素数,对素数计数。所以重点在于对素数的判断方法。
方法一:对于每个数,判断它是否有大于1,并小于它自身的因数。假定num>1,且num=a*b,那么必有 min(a, b) <= sqrt( num )。
//n是素数吗?
bool isPrime(int n)
{
int max=sqrt(n);
for(int i=2;i<=max;i++)
{
if(n%i==0)
return false;
}
return true;
}
//素数计数
int countPrimes(int n) {
int count=0;
for(int i=2;i<n;i++)
if(isPrime(i))
count++;
return count;
}
时间复杂度O(n^2),空间复杂度O(1)。测试用时 832ms。
方法二:可以构建一张表,填上所有可能是素数的数。如果找到一个素数,就把它2倍及以上的倍数从表中划掉,最后表中剩下的全是素数。
int countPrimes(int n) {
if(n<2) return 0;
int count=0;
bool flag[n];
//先假定表中包含2~n-1所有的数
for(int i=2;i<n;i++) flag[i]=true;
//已知2是最小的素数
for(int i=2;i<n;i++)
{
//如果i是素数,划掉它的倍数
//i*(i-1)已经作为i-1的倍数划掉了,所以只要从i*i开始考虑
if(flag[i])
{
count++;
int j=i;
//为了防止溢出,添加条件j<=n/i
while(j<=n/i && i*j<n)
{
flag[i*j]=false;
j++;
}
}
}
return count;
}
时间复杂度很难直接计算,但是可以保证在第二个for循环中,对每个数的访问次数不超过 log n(N的每个质数因子都可能导致对N的一次访问。最小的质数是2,所以访问次数最多也不超过log N)。那么时间复杂度不超过O(n*logn)。空间复杂度是O(n),测试运行时间16ms。
看其他用户算法,第二个for循环中i的最大值是 sqrt(n)。这是个很有效的优化!对于任意m<=M,假如m有最小质因数a,那么必有a <= sqrt(m) <= sqrt( M )。这样一来时间复杂度降低为不超过 O(sqrt(n) * logn)。
int countPrimes(int n) {
if(n<=2) return 0;
int count=0;
bool flag[n];
//先假定表中包含2~n-1所有的数
for(int i=2;i<n;i++) flag[i]=true;
flag[0]=0;
flag[1]=0;
//已知2是最小的素数
for(int i=2;i<=sqrt(n-1);i++)
{
//如果i是素数,划掉它的倍数
if(flag[i])
{
int j=i*i;
while( j<n)
{
flag[j]=false;
j+=i;
}
}
}
//由于第二个for循环没有遍历整个数组,无法统计所有质数,所以要再遍历一次。
for(int i=0;i<n;i++)
if(flag[i]) count++;
return count;
}
测试运行时间 12ms。