题目
知识点
- 优先队列
- 三指针法
思路
优先队列法
- 关键点在于某个丑数一定是在前面某个丑数*2|*3|*5上得到的,否则无法满足只包含质因数的条件
- 第一次见到丑数就是在紫书上面看到的,当时讲解就是用的优先队列实现的,以例子来说:
第一次:1
(1被认为是丑数),后面的丑数必然是在1
的基础上*2,*3,*5
第二次:1
|2,3,5
,取2
再*2,*3,*5
第三次:1,2,3,5
|4,6,10
-> 排序后:1,2,3,4,5,6,10
,取3
再*2,*3,*5
第四次:1,2,3,4,5,6,10
|6(重复),9,25
- 因此,我们维护一个从小到大的优先队列,每次取当前优先队列最小的元素 *2,*3,*5,并出队,将答案记录在一个set中(因为结果可能重复),在寻找完毕后,输出第n个即可。
三指针法
-
以最开始的一部分过程来说明该算法,| 左侧一列是当前所有的丑数,| 右侧是设置的三个指针(指向某个丑数),一开始丑数为1,那么初始位置都在丑数1上:
初始状态:
1 | 2 3 5
Step1:
*2,*3,*5的指针都指向1,那么都分别*1,得到2,3,5,取最小2,放入 | 左侧丑数集合中,且*2的指针++,得到:
1 | 3 5
2 | 2
Step2:.
*3,*5的指针仍然指向1,那么都分别*1,得到3,5。
*2指针指向2,那么得到2*2 = 4
上面三个得到的最小乘积为3,放入 | 左侧丑数集合中,且*3的指针++,得到:
1 | 5
2 | 2 3
3 |
Step2:.
*5的指针仍然指向1,那么得到15 = 5。
*2和*3指针指向2,那么得到22 = 4和2*3=6
上面三个得到的最小乘积为4,放入 | 左侧丑数集合中,且*5的指针++,得到:
1 |
2 | 3 5
3 | 2
4 |
-
在下一步中,需要注意的是元素的重复,*3指针指在丑数2上,*2指针乘在3上,乘积都为6,而*5的指针在2上,乘积为10。两个乘积都是6且最小,那我们更新元素指针时候两个指针都++即可,即:
1 |
2 | 5
3 | 3
4 | 2
-
按照上述步骤我们就能得到第n个丑数,其实也可以说是一个DP问题
代码
优先队列法
class Solution {
public:
int mult[3] = {2, 3, 5};
priority_queue<double, vector<double>, greater<>> q;
unordered_set <double> s;
int nthUglyNumber(int n) {
q.push(1);
int cnt = 0;
while (true) {
int base = q.top();
q.pop();
cnt++;
if (cnt == n) return base;
for (double i:mult){
double ans = base * i;
if (!s.count(ans)){
s.insert(ans);
q.push(ans);
}
}
}
}
};
三指针法
- 似乎是
vector
操作的问题,效率并没有想象中那么快
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 = dp[p2] * 2, num3 = dp[p3] * 3, 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];
}
};