目录
题目链接
https://leetcode-cn.com/problems/count-primes/
题目
统计所有小于非负整数 n
的质数的数量。(注意,这里说的是<n,不包括n)
示例
示例 1:
输入:n = 10
输出:4
解释:小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。示例 2:
输入:n = 0
输出:0示例 3:
输入:n = 1
输出:0
提示
思路
1. 暴力枚举法
这题其实相信很多人在学C/C++课的时候都做过,最简单的方法就是暴力枚举。
通过两层for循环对每个数是否是素数进行判断,第一层for从2开始遍历每个数,第二层for判断这个数是不是是不是质数。
判断方法也很简单,把这个数分别与之间的每个数取余看余数是否为0即可。
那么为什么到即可呢?
因为如果 是 的一个因数,那么必然也是 的因数,因此我们只要判断 和 中更小的那个数即可,也就是说如果在区间存在一个因素的话,其对应的另一个因数必然在区间,因此我们只要在前面这个区间判断即可,也就不需要对所有的数进行判断。
举个例子:拿36来说,它的因数是2,3,4,6,9,12,18,即2*18,3*12,4*9,6*6,12是它的质数,那么肯定也存在一个的数与之对应,即3。
暴力枚举的代码如下:
class Solution {
public:
bool isPrime(int x) {
for (int i = 2; i * i <= x; ++i) {
if (x % i == 0) {
return false;
}
}
return true;
}
int countPrimes(int n) {
int ans = 0;
for (int i = 2; i < n; ++i) {
ans += isPrime(i);
}
return ans;
}
};
那么问题来了,在Leetcode提交时超时了,暴力枚举的时间复杂度高达 。
这里,我们介绍一种很有意思的算法,由希腊数学家厄拉多塞(Eratosthenes)提出的厄拉多塞筛法,简称埃氏筛法。
2. 埃氏筛法
埃氏筛法原理很简单,如果x是一个质数,那么2x、3x、4x...一定不是质数,比如3是一个质数,那么6、9、12...一定是质数,如下图所示(图源:magicalchao):
基于以上思想,就可以省去大量的取余运行,节省大量时间,代码如下:
int countPrimes(int n) {
int count = 0;
vector<bool> signs(n, true);//初始化一个大小为n的bool数组,默认所有的数都是质数
for (int i = 2; i < n; i++) { //从2开始遍历每个数,1不参与遍历,也不会计入count
if (signs[i]) { //判断当前遍历的数时候为质数,如果不是质数就遍历下一个数
count++; //计数器,统计质数个数
for (int j = i + i; j < n; j += i) { //排除当前这个质数的2n、3n倍数....
signs[j] = false;
}
}
}
return count;
}
对于以上解法,仔细观察我们会发现,这个算法还有优化空间,比如对于15这个数,即是3的倍数也是5的倍数,当我们遍历到3的时候已经将15判断为了fasle,不是质数,而当遍历到5的时候,又重复判断了一遍,造成了冗余,对于以上问题,可以采用线性筛的方法。
此外,我们定义signs数组采用的是bool型,这将造成存储空间的浪费,有一种解决方案是采用比特表(Bitmap)算法进行处理,对于上面两种算法下次再更,掌握了埃氏筛的思想,也已经比暴力枚举好多了。
———————————————————未完待续————————————————————