剑指 Offer 49. 丑数
【多路归并(三指针) & 优先队列】
- 求第n个丑数是多少
- 丑数:质因子只包含2、3、5的数
- 思路:
- 若一个数
x
为丑数,那么2*x
,3*x
、5*x
都为丑数
- 就有了三条链表:
-*2: ugly[1]*2 -> ugly[2]*2 -> ugly[3]*2 -> ugly[4]*2 ...
-*3: ugly[1]*3 -> ugly[2]*3 -> ugly[3]*3 -> ugly[4]*3 ...
-*5: ugly[1]*5 -> ugly[2]*5 -> ugly[3]*5 -> ugly[4]*5 ...
- 合并链表:
- 将这三条有序链表合并,使得合并后的链表有序
- 合并链表思想,两条链表合并&k条链表合并
- 手动合并(三指针) & 最小堆(优先队列)
- 合并链表思想,两条链表合并&k条链表合并
- 不同链表间存在重复元素怎么办,会不会被重复计入?
- 手动合并:不会
- 每条链表都是升序排列,合并时也是从小到大排列。每次从不同链表中取最小的
- 所以不同链表中,若存在重复元素,在该元素前面的元素都比它小,被提前拿走了,所以不同链表间的重复元素一定是在同一个循环里被判断的,可当场去重
- 优先队列:可能会
- 所以要加判断
- 同手动合并的规则,因为是从小到大排列,如果存在重复元素,只可能是排列后的最后一个元素,和堆顶元素存在重复,特判即可
- 手动合并:不会
- 将这三条有序链表合并,使得合并后的链表有序
- 若一个数
手动合并:
int nthUglyNumber(int n) {
vector<long> ugly(n+2, 0);
int k = 1;
int p2 = 1, p3 = 1, p5 = 1;
long product2 = 1, product3 = 1, product5 = 1;
while(k <= n){
long minn = min(product2, min(product3, product5));
ugly[k++] = minn;
if(minn == product2){
product2 = 2 * ugly[p2++];
}
if(minn == product3){
product3 = 3 * ugly[p3++];
}
if(minn == product5){
product5 = 5 * ugly[p5++];
}
}
return ugly[n];
}
优先队列(最小堆):
//上面的思路:若X为丑数,则2*x,3*x,5*x也为丑数
int nthUglyNumber(int n){
// vector<long> ugly(n+2, 0);
long ugly = 1;
vector<int> factor{2,3,5};
int k = 1;
priority_queue<long, vector<long>, greater<long>> q;
q.push(1ll);
while(k <= n){
long minn = q.top();
q.pop();
// ugly[k++] = minn;
ugly = minn;
k++;
while(!q.empty() && q.top() == minn) q.pop();
for(int fac: factor){
q.push(minn * fac);
}
}
// return ugly[n];
return ugly;
}
//手动合并的思路:三指针
int nthUglyNumber(int n){
vector<long> ugly(n+2, 0);
vector<int> factor{2, 3, 5};
priority_queue<node> q;
int k = 1;
for(int i = 0; i < factor.size(); i++){
q.push(node{1, factor[i], 1});
}
while(k <= n){
node t = q.top();
q.pop();
if(t.pro != ugly[k-1]) ugly[k++] = t.pro;
q.push(node{ugly[t.id] * t.fac, t.fac, t.id+1});
}
return ugly[n];
}
struct node{
long pro;
int fac;
int id;
friend bool operator<(const node &a, const node &b){
return a.pro > b.pro;
}
};