前言
判断质数,学习到的东西还是蛮多的
一、题目
统计所有小于非负整数 n 的质数的数量。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/count-primes
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
二、思考
1.朴素解法
判断一个数是不是质数的朴素解法就是枚举这个数之下的所有自然数(除1)之外进行取余,如果进一步的话,可以想到只需要判断sqrt(n)之下的所有的自然数(除1)之外的偶数.
def countPrimes(n):
if n <= 2 :
return 0
if n == 3:
return 1
count = 1
for i in range(3,n,2): #选择偶数
cou = 1
for j in range(2,int(i**0.5)+1): #选择到sqrt(i)
if i%j == 0 :
cou = 0
break
if cou == 1 :
count += 1
return count
然而这么算会超出时间限制。进度:19/20
观察到大于等于5的质数都在6的倍数±1区间内,但在6倍数±1区间内的不一定是质数,可以依据这条规律,加大判定步长:
def countPrimes(n):
if n <= 2 :
return 0
if n == 3:
return 1
count = 2
for i in range(5,n,2): #选择偶数
cou = 1 #先标记为质数
if i%6 == 1 or i%6 == 5: #需要在6的倍数左右
for j in range(5,int(i**0.5)+1,6):
if i%j == 0 or i%(j+2) == 0 :
cou = 0 # 有因子,标记为非质数
else : #不在直接标记为非质数
cou = 0
count += cou
return count
仍然超出时间限制,进度:19/20
2.埃氏筛法 Eratosthenes sieve method
对于1~n的自然数,筛选出n以内所有的质数,可以先从底层开始递进往上,如2为质数,那么2以上的所有倍数都不是质数,3同理,4的话已经被2筛出,所以需要同时列出一个数组索引来记录此位置有没有被筛出,下面用1表示是质数 ,0表示不是。
def countprimes(n):
if n <= 2 :
return 0
array = [1] * n #生成数组索引标记此位置是不是质数 初始默认都是质数
array[0] = 0
array[1] = 0
for i in range(2,int(n**0.5)+1): #对sqrt(n)以内的数进行标记
if array[i]:
array[i*i:n:i]=[0]*len(array[i*i:n:i])
return sum(array)
# Run time: 180ms Memory consump:37.4MB
埃氏筛法可以作很多的优化,可以注意到n以内的偶数除2以外肯定不是质数,所以可以在生成记录数组的时候将偶数直接指为0,即不是质数:
def countprimes2(n):
if n <= 2 :
return 0
array = [1,0] * (n//2) # 对标记数组偶数直接标记为否定
if n%2:
array.append(0) #n为奇数,需要补加
array[1] = 0
for i in range(2,int(n**0.5)+1):
if array[i]:
array[i*i:n:i]=[0]*((n-1-i*i)//i+1)
#array[i*i:n:i]=[0]*len(array[i*i:n:i]) 这行与上行效果相同,即把该质数的倍数都打标记为0.
return sum(array)+1
#Run time:100ms Mermory consump: 33.5MB
感觉相当不错了
3.欧拉筛法 Euler sieve method
埃氏筛法筛选的时候,很多时候会造成重复的操作,比如3 和 7,它们都会把21位置的索引重写一次,尽管在3的时候就已经将其写为0即标记不是质数了,所以为了不做这种重复性的操作,欧拉筛的作法是,每次筛选都要以其最小质数因子来将其筛出,为此就需要把筛出来的质数单独放在一个数组中,所以:
def countprimes1(n):
if n <= 2 :
return 0
count = 0
primes = []
index = [1] * n #生成数组索引标记此位置是不是质数 初始默认都是质数
for i in range(2,n):
if index[i]:
primes.append(i) # 将筛选出的质数放入单独的数组
count += 1
j = 0
while j < count and i * primes[j] < n :
index[i * primes[j]] = 0 #将新加入的质数与已经加入的进行乘法运算
if i % primes[j] == 0: #如果满足此条件,则其不是下一个列表内质数与其乘积的最小质因子,跳出
break
j += 1
return count
#Run time:964ms Mermory consump: 30.3MB
欧拉筛法的内存占用比较少的,但是这个耗时好像有点多了。
4.米勒-拉宾素性检验 Miller-Rabin method
一种基于概率的判断,等看懂更新🤣🤣🤣