当前位置: 首页 > article >正文

【最小堆】【动态规划】力扣264. 丑数 II

方法一:堆

class Solution {
public:
    int nthUglyNumber(int n) {
        vector<int> factors = {2, 3, 5};
        unordered_set<long> seen;
        priority_queue<long, vector<long>, greater<long>> heap;
        seen.insert(1L);
        heap.push(1L);
        int ugly = 0;
        for(int i = 0; i < n; i++){
            long curr = heap.top();
            heap.pop();
            ugly = (int)curr;
            for(int factor : factors){
                long next = curr * factor;
                if(!seen.count(next)){
                    seen.insert(next);
                    heap.push(next);
                }
            }
        }
        return ugly;
    }
};

时间复杂度:O(nlogn)。得到第 n 个丑数需要进行 n 次循环,每次循环都要从最小堆中取出 1 个元素以及向最小堆中加入最多 3 个元素,因此每次循环的时间复杂度是 O(log(3n)+3log(3n))=O(logn),总时间复杂度是 O(nlogn)。

空间复杂度:O(n)。空间复杂度主要取决于最小堆和哈希集合的大小,最小堆和哈希集合的大小都不会超过 3n。

我们可以使用堆来模拟丑数的情况,我们定义一个堆,堆顶是最小的数,这个时候我们让堆顶的数乘以factors的三个不同因子的情况,继续加入到堆中,然后将堆顶元素弹出,这个时候堆顶元素就是第二小的数(包含已弹出的最小数),也就是第二个丑数,然后不断继续将堆顶元素乘以factors中顶三个不同因子,得到更多丑数。由于我们遍历了n次,要遍历第n次的时候,前面已经弹出了n-1个丑数,那么第n个数就是第n个丑数。有人可能会问那么有重复的丑数怎么办,那就定义一个哈希集合检查,如果乘以因子后的丑数已经出现在哈希集合中,那么就不会推入到堆里。


方法2:动态规划

class Solution {
public:
    int nthUglyNumber(int n) {
        vector<int> dp(n+1);
        dp[1] = 1;
        int p2 = 1, p3 = 1, p5 = 1;
        for(int i = 2; 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];
    }
};

时间复杂度:O(n)。需要计算数组 dp 中的 n 个元素,每个元素的计算都可以在 O(1) 的时间内完成。

空间复杂度:O(n)。空间复杂度主要取决于数组 dp 的大小。

这个动态规划的思路就是,每一个丑数都可以乘以2、3、5中的某一个因子变成另外一个数,然后由于节省空间,我们用指针来记录将哪一个丑数放在dp哪个位置。

举个例子,dp[1]为1,这时候三个指针都指向1,我们要找dp[2]要放什么呢?这时候有1x2=2, 1x3=3, 1x5=5。这时候我们要将他们正确插入到dp中,这时候就找其中最小的数也就是2放到dp[2]中,dp[2]中放的是1x2。继续找dp[3]要存放什么呢,这个时候有1x3=3, 1x5=5,还有dp[2]乘以2也就是1x2 x 2=4。那么三者中3最小,所以dp[3]中存放的是1x3=3,这个时候令p3++指向2。

通过上面我们可以明白,每一个dp[i]存放的实际上都是从1开始不断乘以某个因子后形成的丑数,如1x2或者1x3或者1x2x2之类的数,而我们要做的,就是将还没计算过的dp[i]乘以各个因子(dp[i]本身就是由许多因子组成)。


http://www.kler.cn/a/518605.html

相关文章:

  • 想品客老师的第七天:闭包和作用域
  • 分布式微服务系统简述
  • CentOS 上安装 Go (Golang)
  • [b01lers2020]Life on Mars1
  • 【PowerQuery专栏】PowerQuery的M语言函数Access数据库访问
  • 全连接神经网络(前馈神经网络)
  • 【Elasticsearch】eland是啥?
  • F#语言的图形用户界面
  • MarsCode青训营打卡Day11(2025年1月24日)|稀土掘金-373.字母出现次数的统计
  • 【OpenGL】OpenGL游戏案例(一)
  • Leetcode-两数之和
  • 【第六天】零基础入门刷题Python-算法篇-数据结构与算法的介绍-一种常见的贪心算法(持续更新)
  • flutter跨端UI框架简介
  • 简识JVM中的STW
  • 【二叉树】4. 判断一颗二叉树是否是平衡二叉树。5. 对称二叉树。6. 二叉树的构建及遍历 7. 二叉树的分层遍历 。
  • 使用 .NET Core 6.0 Web API 上传单个和多个文件
  • 12、MySQL锁相关知识
  • 分布式系统架构怎么搭建?
  • Flutter_学习记录_导航和其他
  • 小程序电商运营内容真实性增强策略及开源链动2+1模式AI智能名片S2B2C商城系统源码的应用探索
  • Linux之NetLink学习笔记
  • docker Ubuntu实战
  • 计算机图形学:实验四 带纹理的OBJ文件读取和显示
  • vue3自定义表格生成动态列
  • linux系统中的 scp的使用方法
  • 【面试题】 Java 三年工作经验(2025)