题目描述
思路解析动画文字版
合数 = 某个质数的倍数。把 2 的倍数、3 的倍数、5 的倍数…… 全划掉,剩下的格子就是质数。这就是筛法。
准备 · 0~29 全标质数候选:绿色 = 暂时当成质数,灰色 = 已划掉。0 和 1 先划掉,剩下 2~29 都先当质数候选。
看 2 · 它是质数:从 2 开始扫。2 还活着,所以它是质数。接着把 2 的倍数(4、6、8…)一个个划掉。
划 2 的倍数 · 4:2 确认为质数(绿)。从 2×2=4 起划:4 被划掉。
划 2 的倍数 · 6:往后跳 2 格:6 划掉(刚划的 4 变灰)。
划 2 的倍数 · 8:再跳一步:8 划掉。第一行的偶数清完。
划 2 的倍数 · 10:进第二行,继续每隔 2 格划一个:10 划掉。
划 2 的倍数 · 12、14:接着划 12、14。
划 2 的倍数 · 16:再隔 2 格:16 划掉。
划 2 的倍数 · 18:再划 18。第二行的偶数也清完了。
划 2 的倍数 · 20、22:进第三行,继续划 20、22。
划 2 的倍数 · 24、26、28:再划 24、26、28。2 的倍数清完——所有偶数都没了。
比 p×p 小的倍数(像 2p)早被更小的质数划过了。划 3 的倍数时,6=2×3 已被 2 划掉,直接从 9=3×3 起步。
看 3 · 还活着:扫到 3,它还活着(2 的倍数里没有 3),所以 3 也是质数。轮到用 3 划倍数。
划 3 的倍数 · 9:3 确认为质数(绿)。从 3×3=9 起划:9 被划掉(6 早被 2 划过,跳过它)。
划 3 的倍数 · 15:每隔 3 往后:12 早被 2 划过(灰,跳过),15 划掉。
划 3 的倍数 · 21、27:继续每隔 3 划:21、27 划掉(18、24 早被 2 划过)。3 的倍数清完。
看 4 · 已是灰的:扫到 4,但它早被 2 划掉了(灰)。合数直接跳过,不用拿它去划倍数。
看 5 · 还活着:扫到 5,它还活着,是质数。从 5×5=25 起划倍数。
划 5 的倍数 · 25:5 确认为质数(绿)。从 25 起划:25 划掉(10、15、20 早被 2、3 划过)。
n=30 时 5×5=25<30 还要看,下一个质数 7 的 7×7=49 已 ≥30,不必再筛——大于 √n 的合数,小因子早被处理过了。剩下的全是质数。
筛完 · 数绿色格子:筛完!绿色的 2、3、5、7、11、13、17、19、23、29 就是小于 30 的全部质数,共 10 个。
雷区实演 · 误把 n 算进去:n=10 时表只到下标 9,绿色质数是 2、3、5、7 共 4 个。表压根不存 10——前提是表长开成 n 而不是 n+1。
边界三连:小 n 是高频坑:n ≤ 2 时答案都是 0。代码开头那句 if n 就是为它兜底。
面试追问:把复杂度的来历和「i*i / √n」两个剪枝讲透,是这题面试的加分点。
参考代码
class Solution: def countPrimes(self, n): if n < 3: return 0 # 小于3没有质数<n is_prime = [True] * n # 先全当质数 is_prime[0] = is_prime[1] = False # 0、1 不是质数 i = 2 while i * i < n: # 筛到 √n 即可 if is_prime[i]: # i 还活着 → 是质数 for j in range(i * i, n, i): # 从 i*i 起划倍数 is_prime[j] = False i += 1 return sum(is_prime) # 数还剩多少 True复杂度
- 时间复杂度:O(n log log n),每个合数只被其质因子划掉,划倍数总次数 ≈ n·Σ(1/p) ≈ n log log n,近似线性
- 空间复杂度:O(n),一张长度为 n 的布尔表 is_prime,记录每个数是否被划掉 → O(n)
易错点
面试追问把动画讲成自己的话
追问埃氏筛的时间复杂度怎么来的?
追问为什么从 i*i 开始划、外层到 √n 停?
追问想再快还有什么办法?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
灯泡开关
LeetCode 319 · 中等 · 沿着 数学套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题