题目1:
给你一个整数 n ,请你找出并返回第 n 个 丑数 。
丑数 就是只包含质因数 2、3 和/或 5 的正整数。
示例 1:
输入:n = 10
输出:12
解释:[1, 2, 3, 4, 5, 6, 8, 9, 10, 12] 是由前 10 个丑数组成的序列。
示例 2:
输入:n = 1
输出:1
解释:1 通常被视为丑数。
提示:
1 <= n <= 1690
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/ugly-number-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解题思路:
方法1:
优先队列。
起始先将最小丑数 1放入队列,
每次从队列取出最小值 xx,然后将 xx 所对应的丑数 2x2x、3x3x 和 5x5x 进行入队。
对步骤 2 循环多次,第 nn 次出队的值即是答案。
为了防止同一丑数多次进队,我们需要使用数据结构 SetSet 来记录入过队列的丑数。
方法2:
多队列归并
可以发现后续的每一个丑数都是又前面经过质因数计算得到的。
那么我们可以将三个队列进行归并,每次取其中的最小值;
直到取到第n个。
//优先队列
class Solution {
int[] nums = new int[] {2,3,5};
public int nthUglyNumber(int n) {
//int越界, 所以取long
Set<Long> set = new HashSet<> ();
//优先队列
Queue<Long> pq = new PriorityQueue<> ();
//
set.add (1L);
pq.add (1L);
for (int i = 1; i <= n ;i++) {
//取优先队列的最小值
long x = pq.poll ();
//取到n时直接return
if (i == n) {
return (int)x;
}
for (int y: nums) {
long ans = x * y;
//如果不在该set中,直接添加
if (!set.contains (ans) ) {
pq.add (ans);
set.add (ans);
}
}
}
return -1;
}
}
//三个队列归并
class Solution {
public int nthUglyNumber(int n) {
// ans 用作存储已有丑数(从下标 1 开始存储,第一个丑数为 1)
int[] ans = new int[n + 1];
ans[1] = 1;
// 由于三个有序序列都是由「已有丑数」*「质因数」而来
// i2、i3 和 i5 分别代表三个有序序列当前使用到哪一位「已有丑数」下标(起始都指向 1)
for (int i2 = 1, i3 = 1, i5 = 1, idx = 2; idx <= n; idx++) {
// 由 ans[iX] * X 可得当前有序序列指向哪一位
int a = ans[i2] * 2, b = ans[i3] * 3, c = ans[i5] * 5;
// 将三个有序序列中的最小一位存入「已有丑数」序列,并将其下标后移
int min = Math.min(a, Math.min(b, c));
// 由于可能不同有序序列之间产生相同丑数,因此只要一样的丑数就跳过(不能使用 else if )
if (min == a) i2++;
if (min == b) i3++;
if (min == c) i5++;
ans[idx] = min;
}
return ans[n];
}
}
参考资料:
【宫水三叶】一题双解:优先队列 & 多路归并