示例
输入: 10
输出: 4
解释: 小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。
思路:
质数就是除了 1 和本身找不到其他能除尽的数,思路请看题目的提示!
思路一:暴力法(超时)
for … else 的用法, 一般配合 break 使用
class Solution:
def countPrimes(self, n: int) -> int:
res = 0
for i in range(2, n):
for j in range(2, i):
if i % j == 0:
break
else:
#print(i)
res += 1
return res
思路二:优化暴力(超时)
验证质数可以不需要小于它的数都验证
考虑12的所有因子:
2×6 = 12
3×4 = 12
4×3 = 12
6×2 = 12
如您所见,不需要进行4×3和6×2的计算。 因此,我们只需要考虑高达√n的因数,因为如果n可被某个数p整除,则n = p×q,并且由于p≤q,我们可以得出p≤√n。
class Solution:
def countPrimes(self, n: int) -> int:
res = 0
for i in range(2, n):
for j in range(2, int(i ** 0.5) + 1):
if i % j == 0:
break
else:
# print(i)
res += 1
return res
思路三:厄拉多塞筛法
让我们看一下第一个数字2。我们知道2的所有倍数一定不能是素数,因此我们将它们标记为非素数。 然后,我们看下一个数字3。类似地,3的所有倍数(例如3×2 = 6、3×3 = 9 …)一定不能是素数,因此我们也将它们标记为非质数。 现在我们看下一个数字4,该数字已经被标记。 这告诉你什么? 您是否也应标记4的所有倍数?
class Solution:
def countPrimes(self, n: int) -> int:
isPrimes = [1] * n
res = 0
for i in range(2, n):
if isPrimes[i] == 1: res += 1
j = i
while i * j < n:
isPrimes[i * j] = 0
j += 1
return res
思路四:综上一起优化
4不是质数,因为它可以被2整除,这意味着4的所有倍数也必须被2整除并且已经被标记。 因此,我们可以立即跳过4并转到下一个数字5。现在,所有5的倍数,例如5×2 = 10、5×3 = 15、5×4 = 20、5×5 = 25,… 可以标记为 这里有一个小的优化,我们不需要从5×2 = 10开始。我们应该从哪里开始标记呢?
实际上,我们可以从5×5 = 25开始标记5的倍数,因为5×2 = 10已经被2的倍数标记了,类似地5×3 = 15已经被3的倍数标记了。因此, 如果当前数是p,我们总是可以标记出从p²开始标记的p的倍数,然后以p的增量标记:p² + p,p² + 2p,…现在终止循环条件是什么?
是的,终止环路条件可以是p <√n,因为所有非素数≥√n必须已经被标记。 循环终止时,表中所有未标记的数字均为质数。
class Solution:
def countPrimes(self, n: int) -> int:
if n < 2: return 0
isPrimes = [1] * n
isPrimes[0] = isPrimes[1] = 0
for i in range(2, int(n ** 0.5) + 1):
if isPrimes[i] == 1:
isPrimes[i * i: n: i] = [0] * len(isPrimes[i * i: n: i])
return sum(isPrimes)