跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

第 n 个丑数:从暴力枚举到动态规划 + 多指针

探讨第 n 个丑数问题的求解方法。首先分析暴力枚举法的低效原因,指出其时间复杂度不可控。随后基于丑数定义,提出动态规划结合多指针的优化方案。通过维护三个指针分别指向待乘以 2、3、5 的丑数位置,每次选取最小值生成新丑数并去重。该方案将时间复杂度降至 O(n),空间复杂度为 O(n)。文章最后总结了从暴力筛选到精准生成的思维升级路径,并讨论了该模式在其他有序序列生成问题中的可扩展性。

ByteFlow发布于 2026/3/21更新于 2026/7/1751 浏览

第 n 个丑数:从暴力枚举到动态规划 + 多指针

在算法训练与工程实现中,'以最小代价生成目标序列' 是一类非常典型的优化场景。第 n 个丑数问题看似简单,却能很好地体现从暴力思路到线性复杂度、从正确性到效率的完整思考路径,也能映射出实际工程中避免无效计算、精准控制状态、减少冗余操作的设计思想。本文以朴素推导为主,不做过度包装,专注思路演进与细节理解。

一、问题回顾

给定整数 n,返回第 n 个丑数。
丑数定义:

  • 只包含质因数 2、3、5 的正整数;
  • 1 是第一个丑数。

前几项丑数:
1, 2, 3, 4, 5, 6, 8, 9, 10, 12, …

要求:在 n 较大时(如 10³、10⁴、10⁵ 级别)仍能稳定、高效地给出结果。


二、最直观思路:暴力判断(从正确性出发)

最容易想到的做法是:

  1. 从 1 开始逐个遍历整数;
  2. 对每个数,判断它是否只由 2、3、5 因子构成;
  3. 统计是丑数的个数,直到找到第 n 个。
核心判断逻辑(伪代码思路)
bool isUgly(int x) { if (x <= 0) return false; while (x % 2 == 0) x /= 2; while (x % 3 == 0) x /= 3; while (x % 5 == 0) x /= 5; return x == 1; }
存在的问题
  • 绝大多数数字都不是丑数,会做大量无效除法与判断;
  • 数字越大,非丑数密度越高,浪费的计算量越大;
  • 时间复杂度不可控,本质是用'筛选'代替'生成',在大规模场景下不具备实用价值。

这也是很多算法题的共同特点:能做对 ≠ 能用,尤其在对延迟、吞吐量有要求的场景中,必须从源头减少无效操作。


三、从定义反推:丑数的生成性质

回到定义:

丑数只由 2、3、5 相乘得到,且 1 是基础丑数。

可以直接得出一条关键性质:
所有丑数,都可以由更小的丑数 ×2 / ×3 / ×5 得到。

这意味着:

  • 我们不需要去判断任何一个数是不是丑数;
  • 只需要从已有的丑数里生成下一个。

思路立刻升级:从'遍历 + 判断'变成'定向生成 + 有序维护'。
但随之而来两个问题:

  1. 生成的数会乱序,无法直接取第 n 个;
  2. 不同路径会生成相同数字(如 2×3=6,3×2=6),存在重复。

四、动态规划 + 多指针:精准生成与去重

为了只生成丑数、按序生成、不重复生成,可以采用动态规划配合多指针的方案。
这套方案的本质是:用空间记录状态,用指针控制迭代,用取最小保证有序,用多分支判断完成去重。

1. 状态设计

定义数组 dp:

  • dp[i] 表示第 i+1 个丑数;
  • 初始化 dp[0] = 1,对应第一个丑数。
2. 指针的意义

既然每个新丑数只能来自:

  • 某个旧丑数 ×2
  • 某个旧丑数 ×3
  • 某个旧丑数 ×5

我们用三个指针分别标记当前应该参与乘法的位置:

  • p2:下一个要 ×2 的丑数下标
  • p3:下一个要 ×3 的丑数下标
  • p5:下一个要 ×5 的丑数下标

初始时都指向 0,即从第一个丑数开始。

3. 递推规则

对每一个新位置 dp[i]:

  1. 计算三个候选值:
    • num2 = dp[p2] * 2
    • num3 = dp[p3] * 3
    • num5 = dp[p5] * 5
  2. 取三者最小值作为当前丑数,保证严格递增;
  3. 如果当前丑数等于某个候选值,就将对应指针后移一位:
    • 若等于 num2,p2++
    • 若等于 num3,p3++
    • 若等于 num5,p5++

这里必须使用独立的 if 而非 else if,目的是:
当多个候选值相等时(如 6 同时由 ×2 和 ×3 得到),多个指针同时后移,从根源上去重,避免后续重复生成同一数字。


五、实现代码(C++)

class Solution {
public:
    int nthUglyNumber(int n) {
        if (n == 1) return 1;
        vector<int> dp(n);
        dp[0] = 1;
        int p2 = 0, p3 = 0, p5 = 0;
        for (int i = 1; i < n; ++i) {
            int num2 = dp[p2] * 2;
            int num3 = dp[p3] * 3;
            int num5 = dp[p5] * 5;
            dp[i] = min(min(num2, num3), num5);
            if (dp[i] == num2) p2++;
            if (dp[i] == num3) p3++;
            if (dp[i] == num5) p5++;
        }
        return dp[n - 1];
    }
};

六、复杂度与可扩展性思考

时间复杂度
  • 只进行一轮从 1 到 n-1 的遍历;
  • 每一步仅包含固定次数的乘法、比较、指针移动;
  • 时间复杂度为 O(n),是该问题理论下界。
空间复杂度
  • 使用长度为 n 的 dp 数组保存历史丑数;
  • 空间复杂度 O(n)。

在工程视角下:

  • 如果 n 上限已知且不大,这种空间换时间的方式非常划算;
  • 若追求极致空间,可以只保留最近几个关键值,但会明显降低可读性与可维护性,除非资源极度受限,否则不建议。
可扩展启发

这套思路不局限于丑数:

  • 只要目标序列满足:由基础因子从已有元素生成;
  • 且需要有序、不重复;
    都可以使用:
    动态规划记录状态 + 多指针控制迭代位置 + 取最小保证有序。

例如:

  • 超级丑数(质因数不限于 2、3、5)
  • 某些带限制的倍数序列生成
  • 流式生成有序目标集合

都可以在这套框架上扩展,只需要调整指针数量与候选生成规则。


七、小结

第 n 个丑数从暴力到最优解的过程,本质是三次思维升级:

  1. 从**'筛选所有数'升级为'只生成目标数'**;
  2. 从**'无序生成'升级为'按序生成'**;
  3. 从**'可能重复'升级为'精准去重'**。

动态规划 + 多指针并没有复杂数学推导,而是用最朴素的方式,把每一步多余计算都砍掉,只保留必要的状态与转移。这种思路在算法训练中是基础,在实际工程中同样适用——很多高性能逻辑,并不是靠花哨技巧,而是靠精准定义状态、严格控制迭代、彻底消除无效计算来实现的。

手动模拟前 10 个丑数的生成过程,观察指针移动与去重逻辑,能更稳定地掌握这一类生成型问题的通用解法。

目录

  1. 第 n 个丑数:从暴力枚举到动态规划 + 多指针
  2. 一、问题回顾
  3. 二、最直观思路:暴力判断(从正确性出发)
  4. 核心判断逻辑(伪代码思路)
  5. 存在的问题
  6. 三、从定义反推:丑数的生成性质
  7. 四、动态规划 + 多指针:精准生成与去重
  8. 1. 状态设计
  9. 2. 指针的意义
  10. 3. 递推规则
  11. 五、实现代码(C++)
  12. 六、复杂度与可扩展性思考
  13. 时间复杂度
  14. 空间复杂度
  15. 可扩展启发
  16. 七、小结
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 临床智能体与环境感知融合:基于 Python 的医疗自然语言处理深度解析
  • Stable Diffusion XL 1.0 助力 AR 滤镜素材批量生成实战
  • Windows 部署 OpenClaw 接入飞书机器人
  • GitHub Copilot 性能优化实战:提升 AI 代码建议响应速度
  • Pywinauto:Windows 桌面应用 Python 自动化教程
  • AI 辅助生成万字长篇小说工具使用指南
  • 数据库 SQL 防火墙:内核级主动防御方案
  • Milvus 实战:Attu 可视化安装与 Python SDK 整合指南
  • Visual C++ 运行库修复指南:解决 Windows 系统组件依赖问题
  • 使用 Python 将 AI 大语言模型接入个人项目
  • JavaScript 基础:替代性跨源技术方案
  • Flutter Web 刷新后页面回退功能失效的解决方案
  • MySQL 数据类型选型实战与避坑指南
  • OpenVLA 模型微调及机器人端部署实战
  • AI 产品经理职业发展路径与核心技术解析
  • ComfyUI Photoshop 插件配置与 AI 绘画工作流实战
  • 构建与 GitHub 深度集成的自动化工作流实战指南
  • 大厂涨薪潮下的前端职业分化与技术进阶路径
  • Pi0 大模型 CPU 环境快速上手 Web 机器人控制
  • JavaScript 内置对象实战:String、Number 与 Array

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online