【每天学习一点算法 2026/01/08】计数质数

每天学习一点算法 2026/01/08

题目:计数质数

给定整数 n ,返回 所有小于非负整数 n 的质数的数量

统计质数数量,什么是质数?就是只能被 1 或 自身整除的数字

  1. 那么我们只需要判断每个数 i 在 [2, i - 1] 范围内是否有能整除 i 的数字,有的话就不是质数,我们先定义一个存放质数的数组,循环判断小于 n 的数字是否是质数,然后将质数放到数组中,最后返回数组长度即可。

    typescript 复制代码
    function countPrimes(n: number): number {
      if (n <= 2) return 0 // 没有小于 2 的质数
      const primes: number[] = []
      a: for (let i = 2; i < n; i++) {
        for (let j = 2; j < i; j++) {
          if (i % j === 0) {
            continue a // 遇到能整除的就直接判断下一个数字
          }
        }
        primes.push(i) // 循环完成都没有跳出说明是质数
      }
      return primes.length
    };

    这个方法遇到大的数字肯定是会超时的,单次检查的时间复杂度是 O (n)

  2. 我们来分析一下,单次检查是否是质数的循环能不能优化一下

    我们很容易就是想到,如果 j 能够整除 i 那是不是, i / j 也能整除 i,那我们只需要校验 j <= i / j 情况下的数字就行了,这样是满足质数条件的循环数就可以大大降低

    typescript 复制代码
    function countPrimes(n: number): number {
      if (n <= 2) return 0 // 没有小于 2 的质数
      const primes: number[] = [] 
      a: for (let i = 2; i < n; i++) {
        for (let j = 2; j <= i / j; j++) {
          if (i % j === 0) {
            continue a
          }
        }
        primes.push(i)
      }
      return primes.length
    };

    提交,后面比较大的数还是会超时。

  3. 埃氏筛:我们要换一个思路了,我们知道如果 i 能被 j 整除 (不是质数)就是表示 i 是 j 的倍数,换而言之只要是 j 的倍数就不可能是质数,那我们创建一个数组,用 1 和 0表示这个位置的代表的数是否是质数,默认将所有数字都标记为 1,从 2 开始循环,首先将2的倍数都标记为 0,那么那么剩余 2 到 2 * 2 之间的 3 为质数,再将 3 的倍数全部标记为 0,以后循环至 n - 1,其实就是找出了小于 n 所有不是质数的数,循环过程中没被标记为 0 的就是质数了。

    typescript 复制代码
    function countPrimes(n: number): number {
      if (n <= 2) return 0 // 没有小于 2 的质数
      const isPrime: number[] = new Array(n).fill(1)
      let count = 0
      for (let i = 2; i < n; i++) {
        if (primes[i]) {
          count++
          for (let j = i + i; j < n; j += i) {
            primes[j] = 0
          }
        }
      }
      return count
    };

    这个方法还可以优化的,我们可以看到 6 既是 2 的倍数又是 3 的倍数,所以里面会有很多重复的判断,上面的取倍数都是从两倍开始取的,但是 [2, i - 1] 的倍数前面都是标记过的,所以我们只需要从第 i 倍开始标记就行了。

    typescript 复制代码
    function countPrimes(n: number): number {
      if (n <= 2) return 0 // 没有小于 2 的质数
      const isPrime: number[] = new Array(n).fill(1)
      let count = 0
      for (let i = 2; i < n; i++) {
        if (primes[i]) {
          count++
          for (let j = i * i; j < n; j += i) {
            primes[j] = 0
          }
        }
      }
      return count
    };
  4. 线性筛: 这就是我们的极限了吗?其实这里面还是有重复标记的情况 比如 2 和 3 都会标记 12,那我们怎么能保证这样的数不被重复标记呢?我们需要保证每个数只被他的最小质因数标记,要怎么做呢?放弃 "遍历质数 → 标记所有倍数",改为 "循环倍数 → 标记已知的质数的倍数"

    • 遍历 2 到 n 的每个数i(不管i是质数还是合数);
    • 用已经找到的质数列表里的质数p,去标记i * p为合数(这里其实标记的时 p 的 i 倍);
    • 核心约束:只有当pi * p的最小质因数时,才标记。

    核心就在于如何保证最小质因数才标记

    假设当前遍历到 6,质数列表里有[2, 3, 5],我们用p遍历质数列表:

    1. i = 6, 质数 p = 2

      • i * p = 12p = 2是 12 的最小质因数 → 可以标记 12 为合数;

      • 此时检查i % p == 0,说明p = 2也是i = 6的最小质因数;

      • 此时必须停止遍历后续质数(比如下一个 p = 3),因为如果继续:

        i * p = 6 * 3=18,但 18 的最小质因数是 2,不是 3 → 如果用 3 标记 18,就违反了 "最小质因数标记" 的规则,会导致 18 后续被 2 重复标记。

    这就相当于我们把倍数放在了最外层的循环,遇到包含不同质因数的倍数时我们只取最小质因数,跳过后续质因数的标记。

    typescript 复制代码
    function countPrimes(n: number): number {
      if (n <= 2) return 0 // 没有小于 2 的质数
      const isPrime: number[] = new Array(n).fill(1)
      const primes: number[] = []
      for (let i = 2; i < n; i++) {
        if (isPrime[i]) {
          primes.push(i)
        }
        for (let j = 0; j < primes.length; j++) {
          if (primes[j] * i > n) break
          isPrime[primes[j] * i] = 0
          if (i % primes[j] === 0) break
        }
      }
      return primes.length
    };

    提交后,评分怎么还不如上面的啊?这对吗?

    题目来源:力扣(LeetCode)

相关推荐
生成论实验室3 分钟前
《事件关系阴阳博弈动力学:识势应势之道》第一篇:生成正在发生——从《即事经》到事件-关系网络
人工智能·科技·算法·架构·创业创新
漂流瓶jz8 分钟前
UVA-1152 和为0的4个值 题解答案代码 算法竞赛入门经典第二版
数据结构·算法·二分查找·题解·aoapc·算法竞赛入门经典·uva
leoufung16 分钟前
LeetCode 76:Minimum Window Substring 题解与滑动窗口思维详解
算法·leetcode·职场和发展
nashane19 分钟前
HarmonyOS 6学习:应用签名文件丢失处理与更新完全指南
学习·华为·harmonyos·harmonyos 5
@codercjw22 分钟前
公差的具体标注方法(书本上/理论上标注方法)
学习
小O的算法实验室29 分钟前
2026年IEEE TETCI,山区环境下基于双种群进化的协同无人机巡逻任务协同优化,深度解析+性能实测
算法·论文复现·智能算法·智能算法改进
生成论实验室1 小时前
《事件关系阴阳博弈动力学:识势应势之道》第二篇:阴阳博弈——认知的动力学基础
数据结构·人工智能·科技·神经网络·算法
久菜盒子工作室1 小时前
时寒冰:第五次产业大转移与未来30年国运:在“双向挤压”中实现惊险一跃
人工智能·学习
风筝在晴天搁浅1 小时前
字节高频题 小于n的最大数
算法
LabVIEW开发1 小时前
LabVIEW水力机组空蚀在线监测
算法·labview·labview知识·labview功能·labview程序