LC313. 超级丑数

题目

超级丑数 是一个正整数,并满足其所有质因数都出现在质数数组 primes 中。
给你一个整数 n 和一个整数数组 primes ,返回第 n 个 超级丑数 。
题目数据保证第 n 个 超级丑数 在 32-bit 带符号整数范围内。

提示:

1 <= n <= 106
1 <= primes.length <= 100
2 <= primes[i] <= 1000
题目数据 保证 primes[i] 是一个质数
primes 中的所有值都 互不相同 ,且按 递增顺序 排列

示例 1:

输入:n = 12, primes = [2,7,13,19]
输出:32 
解释:给定长度为 4 的质数数组 primes = [2,7,13,19],前 12 个超级丑数序列为:[1,2,4,7,8,13,14,16,19,26,28,32] 。

思路

采用最小堆求解超时,官方题解采用动态规划进行解决。
因为并不是dp常用的最小值问题,较难想到,因此记录一下。

首先我们来思考一下最小堆的求解方法:

  1. 把1压入堆,作为第一个丑数
  2. 将堆中最小元素弹出,并将这个元素乘primes[j]后的所有元素入堆。
  3. 直到弹出n个元素(丑数),结束。

可以发现,其中很多元素是用不到的。比如primes=[2,……,100], 如果n=10,根本不需要100,也不需要每次弹出的值t运算后的t*100。这是不太需要的,每次堆插入一个元素,时间复杂度为O(log n)

那怎么能解决这一个问题呐?
首先抛除堆这种数据结构的思维,防止思维定势。
既然问题就出:在于弹出的第i个元素t,到底需要不需要计算t*primes[j]。
如果第i-1个元素ti-1*primes[j]就很大,那么t就不需要乘primes[j]了,因为第i-1个丑数一定小于第i个丑数。说明我们可以先记录最小的两者相乘的值minn,万一这个值被作为第i个丑数(i<n),再计算下一个。

这么来看,我们就需要定义三个数组:

  • dp:用来记录第i个丑数是谁
  • nums:记录还没被标为第i个丑数的最小的t*primes[j]
  • points:记录当前nums[j]是第i个丑数dp[i]与points[j]相乘。

我们发现dp[i]的值受到了前面的dp[i-x]的影响,可以当作动态规划来考虑。
但是因为此处动态规划和平时使用流程不同,状态转移方程难以理解,容易产生误导,就不进行表示了。

具体流程如下:

  • 从下标1-n依次遍历dp数组,并更新dp[i]
  • 对于每一次循环:
    • 找出nums中的最小值nums[j]=minn(找出还没进dp的最小丑数)
    • 更新dp[i]为minn
    • nums中为minn的数nums[j]因被确定为第i个丑数了,因此需要对nums[j]进行更新:
      • 首先points[j]+=1,意思是primes[j]需要和dp中下一个数相乘了(如果minn是dp[i]*primes[j]的结果,那么minn使用后,就需要更新这个i,使nums[j]=dp[i]*primes[j],而此时points[j]记录的则是这个i)
      • nums[j]=dp[points[j]+=1]*primes[j]
  • 输出dp[n]

代码

class Solution {
public:
    int nthSuperUglyNumber(int n, vector<int>& primes) {
        vector<int> dp(n+1);
        int len = primes.size();
        //标记primes[i]下一个要乘的位置
        vector<int> points(len);
        //标记当前primes[i]乘完后的结果
        vector<int> nums(len, 1);
        for(int i=1;i<=n;i++){
            int minn = INT_MAX;
            //找出nums最小值(找出还没进dp的最小丑数)
            for(int j=0;j<len;j++){
                minn = min(nums[j], minn);
            }
            //更新dp[i]
            dp[i] = minn;
            //更新points和nums的值
            for(int j=0;j<len;j++){
                if(nums[j]==minn){
                    points[j]++;
                    //进行乘法溢出判断
                    nums[j] = INT_MAX/primes[j]>=dp[points[j]]? dp[points[j]]*primes[j]:INT_MAX;
                }
            }
        }
        return dp[n];
    }
};


//优先队列超时
// class Solution {
// public:
//     int nthSuperUglyNumber(int n, vector<int>& primes) {
//         priority_queue<long long, deque<long long>, greater<long long>> f;
//         unordered_set<long long> uset;
//         f.push(1);
//         while(n){
//             long long t = f.top();
//             f.pop();
//             if(uset.count(t)) continue;
//             uset.emplace(t);
//             n--;
//             if(n==0) return t;
//             for(int i=0;i<primes.size();i++){
//                 f.push(t*(long long)primes[i]);
//             }
//         }
//         return 0;
//     }
// };

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值