题目描述
超级丑数 是一个正整数,并满足其所有质因数都出现在质数数组 primes 中。
给你一个整数 n 和一个整数数组 primes ,返回第 n 个 超级丑数 。
题目数据保证第 n 个 超级丑数 在 32-bit 带符号整数范围内。
样例描述
示例 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] 。
示例 2:
输入:n = 1, primes = [2,3,5]
输出:1
解释:1 不含质因数,因此它的所有质因数都在质数数组 primes = [2,3,5] 中。
思路
本体整体思路与下面这题类似,
Leetcode–Java–264. 丑数 II
关键:丑数乘以质因数还是丑数,上题只有三个质因数,这题有k个
方法一:
- 优先队列(最小堆)
- 维护所有含有质因数的丑数,不断放入优先队列。然后每次从队列中取出最小的丑数(质因数),乘以数组中的所有质因数,再次放入,连续n次操作就可以得到第
n个了。
方法二:多路归并 + 动态规划
- 维护一个三元组,存储信息如下:
- 这题属于k路归并,维护k个指针,但不需要构造这k个序列。可以每次取出k个指针中最小的那个,然后将该指针往后移动(即将当前序列的下一个值放入堆中)
- 初始化为 (primes[i], i, 0)加入优先队列中,每次从堆中取出最小元素,那么下一个该放入的元素为 (ans[idx + 1] * primes[i], i, idx + 1)
- 去重处理。不需要用Set,因为k个指针指向的序列,以及ans结果都是单调递增的,可以直接比较当前数与ans的最后一位进行比较来实现去重。
代码
方法一:优先队列
class Solution {
public int nthSuperUglyNumber(int n, int[] primes) {
long ugly = -1;
Set<Long> seen = new HashSet<>();
//本身就是升序,也就是小顶堆
PriorityQueue<Long> pq = new PriorityQueue<>();
pq.offer(1L);
seen.add(1L);
for (int i = 0; i < n; i ++ ) {
long curNum = pq.poll();
ugly = curNum;
for (long prime: primes) {
//丑数乘以质因数还是丑数
long x = curNum * prime;
//不存在才加入
if (!seen.contains(x)) {
seen.add(x);
pq.offer(x);
}
}
}
return (int)ugly;
}
}
方法二:动态规划 + 优先队列 + 多路归并
class Solution {
public int nthSuperUglyNumber(int n, int[] primes) {
int m = primes.length;
int ans[] = new int[n];
ans[0] = 1;
//按丑数升序的最小堆
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]);
//先初始化这k个序列
for (int i = 0; i < m; i ++ ) {
pq.offer(new int[]{primes[i], i, 0});
}
int cnt = 1;
//直到结果满了才退出
while (cnt < n) {
//拿到当前k个指针里面最小的数的相关信息: 1.值 2.所在序列下标 3.当前丑数结果集下标
int num[] = pq.poll();
int val = num[0], i = num[1], idx = num[2];
//不是重复才加到结果集
if (ans[cnt - 1] != val) {
ans[cnt] = val;
cnt ++;
}
//加入primes[i]为质因数所在序列的下一个数
pq.offer(new int[]{primes[i] * ans[idx + 1], i, idx + 1});
}
return ans[n - 1];
}
}