题目来源:LeetCode-探索初级算法-数学-计数质数
题意描述: 统计所有小于非负整数 n 的质数的数量。
输入输出示例:
输入: 10
输出: 4
解释: 小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。
解题思路:
Bob:你可知道茴香豆的茴字有几种写法 ?
Alice: ヾ(。ꏿ﹏ꏿ)ノ゙,你说啥,这和质数有啥关系啊 ?你脑子瓦特了 ?
Bob: ┓( ´∀` )┏ 好吧,茴香豆的茴字有好几种写法,求解素数的程序也有好几种写法,这里面考的就是其中一种,筛法。
Alice: 你直接了当的说不就得了,不就是筛法求素数吗?
Bob: 啊,原来你会写啊。
Alice: 我不会,你来写,我给你 code review, ヾ(◍°∇°◍)ノ゙
Bob: 好。
求解素数的第一种方法就是按照定义来:
质数是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。-百度百科。
程序是这样子的:
bool isPrime(int x){
if(x == 2 || x == 3 || x == 5 || x == 7){
return true;
}
if(x <= 1 || x % 2 == 0){
return false;
}
int bound = int(sqrt(x));
for(int i=3; i<=bound; i+=2){
if(x % i == 0){
return false;
}
}
return true;
}
Alice: 等一下,你的代码怎么写的这么奇怪???不应该这么写吗 ?
bool isPrimeByAlice(int x){
if(x < 2){
return false;
}else{
for(int i=2; i<=x-1; i++){
if(x % i == 0){
return false;
}
}
}
return true;
}
Bob: 你听我解释啊,不是你想的那样的 !(ΩДΩ) 我也是按照定义法去判断一个数是不是素数的,只是优化了一下,减少了计算步骤。对于小于 9 的素数直接判断,对于大于 9的素数 X 用从 3 开始的奇数 到 根号 X 一个一个整除的。
Alice: 为什么算到 根号 X 而不是 X-1 啊?
Bob: 因为乘法具有交换性。举个例子 12 = 2 * 6 , 12 = 6 * 2 我们算到 12 被 2 整除就知道12 不是素数了,不用再去算12 能不能被6整除了。那么最少算到哪里好呢? 算到根号12, 大概就是 3.464,最多算到 4, 因为4 * 4 = 16 > 12 了就。对于素数,像 11 也是这样的,算到根号 x 就好了。
Alice: 原来是这样啊,的确快了一些,少了一些计算步骤。 除了2,所有的素数都是都是奇数,奇数不可能被偶数整除,所以只判断了从3开始的奇数。
Bob: 对,就是这样的。
Alice: 你继续讲筛法吧。
Bob: 先来看这个图,一图胜千言。
Bob: 可以这样理解,素数的所有倍数都不是素数,排除法把所有不是素数的去掉剩下的就是素数了。这也是为什么叫筛法的原因,沙里淘金,离开的总是有原因的吧。
Alice: 净扯些没用的,代码呢 ??
bool isPrimeShai(int x){
int flags[100001];
for(int i=0; i<=100000; ++i){
flags[i] = 1;
}
for(int i=2; i<=x; ++i){
if(flags[i] == 1){
for(int j=i*2; j<=x; j+=i){
flags[j] = 0;
}
}
}
return flags[x] == 1;
}
Bob: 这是C++ 版本的,没有用vector
,所以没有动态数组,只能判断10万之内的素数。
Alice: 那我再写个Python。
def isPrime(number):
flags = [1 for z in range(0, number + 1)]
for x in range(2, number + 1):
if flags[x] == 1:
for z in range(x * 2, number + 1, x):
flags[z] = 0
return flags[x] == 1
Bob: ヾ(◍°∇°◍)ノ゙厉害呀。
Alice: 有空把C++还有Java 带“动态数字的”筛法补上。
Bob: ┓ ( ´∀` ) ┏,你叫我补上,我就补上,那岂不是很没有面子。
Alice: 嘿!!(’∇’)シ┳━┳
代码:
class Solution:
def countPrimes(self, n: int) -> int:
// 使用筛法求素数的方法求解
cnt = 0
// 记录 1 到 N 的素数的个数
flags = [1 for x in range(0, n+1)]
// 用于标记一系列数字是否是素数, x 是素数的时候, flags[x] == 1。flags初始化为全1的数组即首先假设所有数字都是素数。
for x in range(2, n):
// 从2开始遍历,因为2是一个真正的素数。
if flags[x] == 1:
cnt += 1
// 如果有一个素数,计数加一,然后
for z in range(2*x, len(flags), x):
// 这个素数的所有倍数都不是素数,注意看循环。
flags[z] = 0
return cnt
// 最后flags里面标记为1的位置对应的数都是素数,cnt记录了素数的个数。
易错点:
总结: