【题目】求n以内的素数个数

最近在leetCode上刷提,还是满锻炼人的,为以后面试打基础吧。不多说下面开始。

 

问题:求[2,n]之间的素数的个数。

来源:leetCode OJ

提示:

Let's start with a isPrime function. To determine if a number is prime, we need to check if it is not divisible by any number less than n. The runtime complexity of isPrime function would be O(n) and hence counting the total prime numbers up to n would be O(n2). Could we do better?

 

先让我们写一个函数isPrime用于判断给定的数是否是素数,为了判别总一个数是否是素数,我们需要依次检查比这个数小的数能否整除n。那么函数isPrime的时间复杂度就将是O(n),因此总体的时间复杂度就会达到O(n2),我们能做的更好吗?

 

 

第一次尝试

预备知识

① 什么是素数:素数(又叫质数),与之相反的是合数。如果只考虑非负数范围内,一个大于1的数,它只能被 1 和 他本身整除。这样的数就是素数。

② 0 和 1既不是质数也不是合数。

③ 素数定理:如果一个数x是素数,那么在整数范围[2,√x ]之间,找不到任何能整除x 的整数。因此,我们不必对 [2, n) 的所有整数去尝试,而只需要对 [2,√x]之间的数尝试整除就OK了,节约了时间。

 

#include<iostream>
#include<cassert>
#include<time.h>
#include<cmath>
using namespace std;

/**
作用:判断一个数是否是素数 
参数x:待判断的素
返回:是素数返回true,否则返回false 
*/
bool isPrime(int x)
{
    for(int i=2;i<=int(sqrt(x));++i)
    {
        if(x%i==0) return false;
    }
    return true;
}

/**
作用:统计[2,n]之间的素数的个数 
参数:n
返回:素数的个数 
*/
int countPrimes(int n) {
    int count=0;
    
    if(n<2) return 0;

    for(int i=2;i<=n;++i)
    {
        if(isPrime(i))   //如果 i为素数
        {
            ++count;
        }
    }
    return count;

}


int main()
{
    
    clock_t start = clock();
    int total = countPrimes(700000);
    clock_t end   = clock();
    
    cout<<"耗时:"<<(double(end-start))/CLOCKS_PER_SEC*1000<<"毫秒"<<endl;
    
    
    return 0;
}


/**********测试数据**************************
n=200000    81毫秒 
n=700000    385毫秒
*/

 

这是很多人第一感觉写出来的solution,它最大问题在于使用了sqrt函数。第一:sqrt是用来处理浮点数的,而浮点数的计算速度远远慢于integer。第二,函数调用也会造成时间的浪费。第三:浮点数的存储误差可能引出致命错误,如  sqrt(9)  可能等于 2.9999999 ,那么 int(sqrt(9)) 就等于2 而不是3。

但是我从来都不是这样写的,这个让我印象很深,在我的编程启蒙书 《C prime Plus》书中,它教我使用  C/C++内置于语言的运算符乘 * ,而不是sqrt()函数。

第二次尝试

 

#include<iostream>
#include<cassert>
#include<time.h>
#include<cmath>
using namespace std;

/**
作用:判断一个数是否是素数 
参数x:待判断的素
返回:是素数返回true,否则返回false 
*/
bool isPrime(int x)
{
    for(int i=2;i*i<=x;++i)   //改进:使用 i*i<=x  而不是 i<= sqrt(x)  
    {
        if(x%i==0) return false;
    }
    return true;
}

/**
作用:统计[2,n]之间的素数的个数 
参数:n
返回:素数的个数 
*/
int countPrimes(int n) {
    int count=0;
    
    if(n<2) return 0;

    for(int i=2;i<=n;++i)
    {
        if(isPrime(i))   //如果 i为素数
        {
            ++count;
        }
    }
    return count;

}


int main()
{
    
    clock_t start = clock();
    int total = countPrimes(700000);
    clock_t end   = clock();
    
    cout<<"耗时:"<<(double(end-start))/CLOCKS_PER_SEC*1000<<"毫秒"<<endl;
    
    
    return 0;
}


/*********************测试数据*****************************
n=200000    46毫秒 
n=700000    194毫秒
*/

 

可以发现,小小的改变,时间复杂度减少了大半!!!让我觉得很遗憾的是,很少有人知道这个技巧,leetCode上,也没有发现使用这个技巧的。

 

第三次尝试

前面的2个solution是不能Accept的,时间复杂度不达标。下面我又开始了google,找到了如下的优化方案。

预备知识:

① 合数一定能分解为 若干个 质素相乘 。如 27 = 3x3x3 ,155 = 5x31

② 原命题的真假性 和他的逆否命题相同。因此我们可以推导出第①命题的逆否命题--->: 不能分解为 素数相乘的数一定是质素(素数)。

因此根据这个推导出的定理,我们不再需要将待判断的所有数x去对 [2,sqrt(x)]之间的数试除,而只需要去对 [2,sqrt(x)]之间的素数试除就OK了。所以:我们必须在迭代判断素数的过程中,将已经判定为素数的数用数组存储起来,因为后面更大的数判断时,需要用比他小的素数(在[2,sqrt(x)]之间的)去试除做判断。那么这个数组需要多大呢?

[0,n]之间有 n +1 个整数,然而0 和 1不是素数也不是质素,因此剩下 n +1 -2 = n-1 个数。素数偶数各一半,除了2,偶数一定不是素数,因此我们大致将这个数组大小定义为  (n-1)/2 +1 

好了,时间 和 空间上都过了优化,下面试试吧。

 

#include<iostream>
#include<cassert>
#include<time.h>
#include<cmath>
using namespace std;

/**
作用:判断一个数是否是素数 
参数x:待判断的素
参会primes:存储比x小的所有素数 的数组 
返回:是素数返回true,否则返回false 
*/
bool isPrime(int x,int *primes)
{
    for(int i=0;primes[i]*primes[i]<=x;++i) //用primes中存储的素数做为试除因子。 
    {
        if(x%primes[i]==0) return false;
    }
    return true;
}


/**
作用:统计[2,n]之间的素数的个数 
参数:n
返回:素数的个数 
*/
int countPrimes(int n) {
    
    if(n<2) return 0;
    
    int count=0;
    int* primes = new int[(n-1)/2+1] ;
    
    if(n >=2) primes[count++] = 2;   //2是第一个素数,直接将他放入数组。 

    for(int i=3;i<=n;++i)
    {
        if(isPrime(i,primes))   //如果 i为素数
        {
            primes[count++] = i; //则将 i存储到数组中 
        }
    }

delete[] primes;
return count; } int main() { clock_t start = clock(); int total = countPrimes(700000); clock_t end = clock(); cout<<"耗时:"<<(double(end-start))/CLOCKS_PER_SEC*1000<<"毫秒"<<endl; return 0; } /****************测试数据****************** n=200000 13毫秒 n=700000 50毫秒 */

 

时间复杂度又大大减少了!!!可见,我们的程序优化的空间往往很大。

最后,我的程序终于被AC了 ,不过,还可以继续优化,下面是我的程序的所处的时间复杂度的位置,前面有70% 的solution比我更优 。优化尚未成功,码农仍需努力。

 :)

 

 

 

 

 更佳的solution 请看这篇

 

 

 欢迎转载,请注明出处:www.cnblogs.com/lulipro

 为了获得更好的阅读体验,请访问原博客地址。

限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。

代码钢琴家

 

### 回答1: 题目:7-5n以内最大的k个素数以及它们的和。 解题思路: 1. 判断一个数是否为素数的方法:从2到该数的平方根之间的所有数都无法整除该数,即为素数。 2. 从2到n进行遍历,判断每个数是否为素数,并将素数存入一个列表中。 3. 对素数列表进行排序,取前k个数,并计算它们的和。 代码如下: ### 回答2: 为了n以内最大的k个素数以及它们的和,我们可以先写一个函数来判断一个数是否为素数。可以用试除法来判断,即用2到该数开方之间的所有整数去除该数,如果都不能整除,则该数为素数。代码如下: int isPrime(int n) { // 判断n是否为素数 if (n < 2) return 0; // 小于2的数均不为素数 for (int i = 2; i <= sqrt(n); i++) { if (n % i == 0) { return 0; // 能被整除则不是素数 } } return 1; // 其他情况为素数 } 然后我们可以用一个vector来存储n以内的所有素数,再从中选择最大的k个,并计算它们的和。代码如下: vector<int> getPrimes(int n) { // 获取n以内的所有素数 vector<int> primes; for (int i = 2; i <= n; i++) { if (isPrime(i)) { primes.push_back(i); } } return primes; } vector<int> getKMaxPrimes(int n, int k) { // 获取n以内最大的k个素数 vector<int> primes = getPrimes(n); sort(primes.rbegin(), primes.rend()); // 倒序排序 return vector<int>(primes.begin(), primes.begin() + k); } int sumOfKMaxPrimes(int n, int k) { // 获取n以内最大的k个素数的和 vector<int> kMaxPrimes = getKMaxPrimes(n, k); int sum = 0; for (int num : kMaxPrimes) { sum += num; } return sum; } 以上就是n以内最大的k个素数以及它们的和的完整代码。需要注意的是,当n比较大时,getPrimes函数的效率可能比较低,可以考虑使用更高级的筛法优化。 ### 回答3: 首先需要明确什么是素数素数就是只有1和本身两个因数的自然数,例如2、3、5、7等就是素数。那么n以内最大的k个素数以及它们的和,我们可以采用筛法。 我们可以采用埃氏筛法进行筛选。先将2到n之间的所有整数标记为素数,然后按照从小到大的顺序依次枚举素数p,沿着p的小于等于n的倍数,将其标记为合数。这里的“标记为合数”实际上就是用一个bool类型的数组is_prime来记录是否为素数,当is_prime[i]为false时,表示i不是素数,当is_prime[i]为true时,表示i是素数。当p大于sqrt(n)时,剩下的没有标记为合数的数就是素数。 以下是具体实现的代码: ```C++ #include <iostream> #include <cstring> #include <cmath> using namespace std; const int MAXN = 100010; int prime[MAXN]; // 记录素数 bool is_prime[MAXN]; // 记录是否为素数 void get_primes(int n, int k) { // n: 上界,k: 要素数个数 memset(is_prime, true, sizeof(is_prime)); // 初始化全部为素数 int cnt = 0, sum = 0; for(int i = 2; i <= n; i++) { if(is_prime[i]) { prime[++cnt] = i; sum += i; if(cnt == k) break; // 找到k个素数之后退循环 for(int j = i * i; j <= n; j += i) is_prime[j] = false; // 将i的倍数标记为合数 } } cout << "最大的" << k << "个素数为:\n"; for(int i = cnt; i >= 1; i--) cout << prime[i] << " "; // 倒序输出 cout << endl; cout << "这" << k << "个素数的和为:" << sum << endl; } int main() { int n, k; cout << "输入上界n:"; cin >> n; cout << "输入素数个数k:"; cin >> k; get_primes(n, k); return 0; } ``` 时间复杂度为O(nloglogn),在n较小的情况下可以忽略不计。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值