在问 · 算法题解

204. 计数质数

中等数学埃拉托斯特尼筛力扣原题 ↗

题目描述

输入整数 n,统计严格小于 n 的质数有多少个,返回这个个数。

质数是大于 1、且只能被 1 和自身整除的整数。

示例 1
输入:n = 10
输出:4

解释:小于 10 的质数是 2、3、5、7,共 4 个。

示例 2
输入:n = 0
输出:0

解释:没有小于 0 的质数。

示例 3
输入:n = 1
输出:0

解释:没有小于 1 的质数。

数据范围

  • 0 <= n <= 5 * 10^6

解题思路:埃拉托斯特尼筛

埃拉托斯特尼筛。先假设 2..n-1 都是质数,从小到大枚举 i;如果 i 还被标为质数,就把它的倍数 i·i, i·i+i, … 全部划掉。只需枚举到 i·i < n,因为任何合数都有一个不超过其平方根的质因子。最后数一下还剩多少个质数。

为什么正确

处理完 i 后,所有含有不超过 i 的质因子的合数都已被划掉。

复杂度分析

时间 O(n log log n),空间 O(n)。

易错点

题目要的是“小于 n”,不包含 n 本身。内层从 i·i 开始划即可,更小的倍数已经被更小的质因子划过;在 C++/Java 里 i·i 可能溢出,要用 long。逐个试除判断质数在 n = 5×10^6 时会超时。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def countPrimes(self, n: int) -> int:
        if n < 3:
            return 0
        is_prime = [True] * n
        is_prime[0] = is_prime[1] = False
        i = 2
        while i * i < n:
            if is_prime[i]:
                for j in range(i * i, n, i):
                    is_prime[j] = False
            i += 1
        return sum(is_prime)

载入编辑器,自己跑一遍 →

其他解法

埃氏筛中一个合数会被它的每个质因子重复标记;线性筛只让最小质因子去标记,去掉了重复工作,从 O(n log log n) 降到 O(n)。

解法 2:线性筛(欧拉筛) 时间 O(n),空间 O(n)

从小到大枚举 i,如果 i 没有被标记就把它加入质数表。然后用质数表中的每个质数 p 去标记 i*p 为合数;一旦 i 能被 p 整除就停止,因为更大的质数 q 乘以 i 得到的合数,其最小质因子是 p 而不是 q,应该留给后面的 i 去标记。这样每个合数只会被它的最小质因子标记一次。

每个合数只标记一次,理论复杂度严格 O(n),并且顺便得到了质数表。需要额外保存质数列表,“i 能被 p 整除就停止”这一步需要理解才能写对。

class Solution:
    def countPrimes(self, n: int) -> int:
        if n < 3:
            return 0
        # 偶数中只有 2 是质数,只对奇数做线性筛,奇数 i 只需乘奇质数 p
        is_composite = bytearray(n)
        primes = []
        for i in range(3, n, 2):
            if not is_composite[i]:
                primes.append(i)
            for p in primes:
                if i * p >= n:
                    break
                is_composite[i * p] = 1
                if i % p == 0:
                    break
        return len(primes) + 1

数学 专题的其他题目

查看 数学 全部题目 →