给出非负整数n,统计所有小于非负整数 n 的质数的数量。
算法:
通过质数的定义来计算一定会超时。评论区充斥着一种牛逼的筛法,如下:
计算小于非负整数n的质数:n<3时,return 0
n>=3时,2是第一个质数,其后所有2的倍数都不是质数,划去;
3是第二个质数,其后所有3的倍数都不是质数,划去;
4已排除,5是第三个质数,所有5的倍数都划去。
——————
算法如下:
class Solution:
def countPrimes(self, n: int) -> int:
if n<3:return 0
ls=[1]*n
ls[0],ls[1]=0,0#0与1不是质数
for i in range(2,int(n**0.5)+1):
if ls[i] == 1:
ls[i*i:n:i] = [0] * len(ls[i*i:n:i])
return len([x for x in ls if x==1])
首先定义一个列表ls=[1]*n
,ls[0],ls[1]=0,0
因为0与1不是质数。
——————————
判断任意一个数n是不是质数,从质数的定义上来说,直觉的方法肯定是对数做循环,如果有除1和n本身外任一小于n的数可以被n整除,那么n就不是素数。
但是,如果它不是质数,那么它一定可以表示成两个数(除了1和它本身)相乘,这两个数必然有一个小于等于它的平方根。只要找到小于或等于的那个就行了。
所以算法上对质数的检验公式一般为
m = int(a ** 0.5) # 根号n取整
i = 2
while i <= m:
if a % i == 0:break # n可以整除i
i += 1
if i > m:print('%s是素数!' % a)
else:print('%s不是素数!' % a)
——————————
回到上面的算法:
for i in range(2,int(n**0.5)+1):
if ls[i] == 1:
ls[i*i:n:i] = [0] * len(ls[i*i:n:i])
对列表来说,索引就代表数字本身,1与0表示是否为质数。
如上,求小于非负整数n以内的质数,最后一个验证的应该是n-1.即for i in range(2,int((n-1)**0.5)+1)
遍历从索引为2开始,按步长为2切片,序列解包,依次将[0]赋值给所有对应索引位。
ls[i*i:n:i] = [0] * len(ls[i*i:n:i])
,为何从ii开始赋值,举例从3开始,第一个被划去的应该是32,但是因为是2的倍数早已被划掉了,所已从33开始。若是11,112是2的倍数,113是3的倍数,……117是7的倍数,由此可知,对每个素数来说,开始赋值的起始位置应该是i*i。
最后需要返回所有小于非负整数 n 的质数的数量,即对列表ls进行遍历,统计有多少个1.
可以写为:len([x for x in ls if x==1])
,使用列表筛选器
考虑到python中字符串的内置方法,还可以''.join(map(str,ls)).count('1')
,但是这个方法本质上对ls进行3次遍历,非常慢,在此仅为多样性来提供,并不推荐。
——————————
小拓展。
ls的索引即数字本身,对return的表达式做修改即可返回所有小于n的质数集。
return [i for i,j in enumerate(ls) if j==1]