背景
Leetcode每日一题 204. 计数质数【简单】
- 看到题第一个想法就是判断每个数是否是质数=》方法一
- 当然最简单的想法会超时 然后就想到了如果某个数x是质数 那么x的倍数必定是合数可以不用判断
后来查了资料知道了原来有个学名 埃氏筛【埃拉托斯特尼筛法】=》方法二 - 继续查看资料了解了 还有一种更加巧妙的方法线性筛 【欧拉筛】 =》方法三
代码
1. 普通判断
这里还对2的倍数进行了预处理 然而还是超时
def countPrimes(n):
"""
:type n: int
:rtype: int
"""
import math
def isPrime(num):
if num==2 or num==3:
return True
if num==1 or num%2==0:
return False
mid = int(math.sqrt(num))+1
for i in range(3,mid,2):
if num%i==0:
return False
return True
ret=0
for i in range(2,n):
if isPrime(i):
ret+=1
return ret
2. 埃氏筛
如果是质数 则将质数的倍数去除
大致流程:
i | 去除 |
---|---|
2 | 4,6,8,10… |
3 | 9,15,21… |
4 | 非质数已去除 |
5 | 25,35… |
… | … |
def countPrimes2(n):
"""
:type n: int
:rtype: int
"""
num = set([i for i in range(2,n)])
for i in range(2,n):
if i in num:
tmp=i*2
while tmp < n:
if tmp in num:
num.remove(tmp)
tmp +=i
return len(num)
3. 欧拉筛
def countPrimes3(n):
"""
:type n: int
:rtype: int
"""
prime=[]
isprime = [True]*n
for i in range(2,n):
if isprime[i]:
prime.append(i)
for p in prime:
if p*i>=n:
break
isprime[p*i]=False
if i%p==0:
break
return len(prime)
这里用prime来存储当前已判断的质数 从小到大
isprime用来记录是否是质数
代码好理解 最关键的就是
if i%p==0:break
这一条语句的目的是为了每一个合数只让最小质因数来判断其为合数
所以将判断次数变成了线性 并保证了不重复不遗漏
因为在prime中的质数是有序的 从小到大
即:prime[j]<prime[j+1]
当i可以被prime[j]整除时 我们可以假设 i= prime[j] * x
那么下一个需要判断的数
value=i*prime[j+1] => prime[j]*prime[j+1]*x
prime[j+1]并不是最小的能整除它的数 所以我们不需要继续判断prime[j]后面的质数与i相乘了
大致流程:
i | prime | isprime变为false |
---|---|---|
2 | [2] | 4 |
3 | [2,3] | 6,9 |
4 | [2,3] | 8 |
5 | [2,3,5] | 10,15,25 |
6 | [2,3,5] | 12 |
… | … | … |
看了流程 我们可以总结
思想还是一样 去除质数的倍数
实现方式与埃氏筛略有差别
埃氏筛:对于质数x 一次性去除所有x的倍数
而欧拉筛:对倍数i进行了遍历 每次去除当前所有质数的i倍
我们以流程中的12举例
当i=4时 我们先判断4*2=8 为合数 此时i%prime[j] 即4%2 余数为零
那么这时我们退出 不再判断 4*3=12 因为3并不是12的最小质因数
12的最小质因数为2
所以下面倍数i=6时 2*6=12 此时判断12为合数
由此可见 每一个合适有且仅有一次被判断为合数 与埃氏筛相比大大减少了判断次数