LeetCode-264. 丑数 II
题目来源
题目详情
给你一个整数 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
题解分析
解法一:优先队列+Set
- 本题其实是一道比较简单的题目,但是需要考虑到题目中所描述的“丑数”的特点,那就是所有的丑数都是2,3,5的倍数,不存在其他可能。
- 为了找到指定的第n个丑数,我们需要从小到大枚举出每个丑数。但是,应该怎么枚举才可以保证顺序的从小打到呢?这个时候,我们可以想到,优先队列中就已经天然地维护了元素的顺序,所以我们可以借助优先队列来存储丑数。
- 此外,需要注意的一点是,单纯依靠优先队列是不够的,我们还需要判重,因为一个数有可能同时是2,3,5的倍数,所以这里会发生重复。
- 最后,还需要注意的一小点是,我们需要使用Long来存储元素,因为在枚举的过程中,n太大会导致中间元素可能超出int的最大值。
class Solution {
public int nthUglyNumber(int n) {
PriorityQueue<Long> que = new PriorityQueue<>(){{
offer(1L);
}};
HashSet<Long> set = new HashSet<>();
long now = 1;
set.add(now);
for(int i=1; i<=n; i++){
now = que.poll();
if(!set.contains(now * 2)){
que.offer(now * 2);
set.add(now * 2);
}
if(!set.contains(now * 3)){
que.offer(now * 3);
set.add(now * 3);
}
if(!set.contains(now * 5)){
que.offer(now * 5);
set.add(now * 5);
}
}
return (int)now;
}
}
解法二:动态规划
这道题一开始死活不明白三指针到底是怎么用的。后来突然就想明白了,我尝试解释一下:
例如 n = 10, primes = [2, 3, 5]。 打印出丑数列表:1, 2, 3, 4, 5, 6, 8, 9, 10, 12
首先一定要知道,后面的丑数一定由前面的丑数乘以2,或者乘以3,或者乘以5得来。例如,8,9,10,12一定是1, 2, 3, 4, 5, 6乘以2,3,5三个质数中的某一个得到。
这样的话我们的解题思路就是:从第一个丑数开始,一个个数丑数,并确保数出来的丑数是递增的,直到数到第n个丑数,得到答案。那么问题就是如何递增地数丑数?
观察上面的例子,假如我们用1, 2, 3, 4, 5, 6去形成后面的丑数,我们可以将1, 2, 3, 4, 5, 6分别乘以2, 3, 5,这样得到一共6*3=18个新丑数。也就是说1, 2, 3, 4, 5, 6中的每一个丑数都有一次机会与2相乘,一次机会与3相乘,一次机会与5相乘(一共有18次机会形成18个新丑数),来得到更大的一个丑数。
这样就可以用三个指针,
pointer2, 指向1, 2, 3, 4, 5, 6中,还没使用乘2机会的丑数的位置。该指针的前一位已经使用完了乘以2的机会。
pointer3, 指向1, 2, 3, 4, 5, 6中,还没使用乘3机会的丑数的位置。该指针的前一位已经使用完了乘以3的机会。
pointer5, 指向1, 2, 3, 4, 5, 6中,还没使用乘5机会的丑数的位置。该指针的前一位已经使用完了乘以5的机会。
下一次寻找丑数时,则对这三个位置分别尝试使用一次乘2机会,乘3机会,乘5机会,看看哪个最小,最小的那个就是下一个丑数。最后,得到下一个丑数的指针位置加一,因为它对应的那次乘法使用完了。
这里需要注意下去重的问题,如果某次寻找丑数,找到了下一个丑数10,则pointer2和pointer5都需要加一,因为5乘2等于10, 2乘5也等于10,这样可以确保10只被数一次。
class Solution {
public int nthUglyNumber(int n) {
int[] dp = new int[n+1];
dp[1] = 1;
int p2 = 1, p3 = 1, p5 = 1;
for(int i=2; i<=n; i++){
int num2 = 2 * dp[p2];
int num3 = 3 * dp[p3];
int num5 = 5 * dp[p5];
dp[i] = Math.min(num2, Math.min(num3, num5));
if(dp[i] == num2){
p2++;
}
if(dp[i] == num3){
p3++;
}
if(dp[i] == num5){
p5++;
}
}
return dp[n];
}
}