求第n个超级丑数。
- 超级丑数:质因子都包含在质数列表primes里的数的正整数
- 思路:同剑指 Offer 49. 丑数的优先队列的思路,有两种方式:
- 方法1:若一个数
x
x
x是超级丑数,则
p
r
i
m
e
s
[
0
]
×
x
primes[0]\times x
primes[0]×x、
p
i
m
e
s
[
1
]
×
x
pimes[1]\times x
pimes[1]×x、
p
r
i
m
e
s
[
2
]
×
x
primes[2] \times x
primes[2]×x…
p
r
i
m
e
s
[
m
−
1
]
×
x
primes[m-1] \times x
primes[m−1]×x都是超级丑数
- 建立优先队列,小根堆
- 去重:
- 因为优先队列每次都是取最小的元素,所以即便是不同方式获得的重复元素,在优先队列中排序必然是相邻的,且要把比它小的都取完之后才轮到它,这个过程中对于这个值所有可能的情况都放在优先队列里了(且它们是相邻的)
- 所以只要取出第一个元素,然后判断栈顶是否相同,相同直接pop掉即可
- 因为优先队列每次都是取最小的元素,所以即便是不同方式获得的重复元素,在优先队列中排序必然是相邻的,且要把比它小的都取完之后才轮到它,这个过程中对于这个值所有可能的情况都放在优先队列里了(且它们是相邻的)
- 复杂度:
- 时间复杂度:
O
(
n
m
l
o
g
(
n
m
)
)
O(nmlog(nm))
O(nmlog(nm)),其中
m
m
m是primes数组的长度
- 需要从堆中弹出 n n n个元素,每次放入 m m m个元素,堆中最多有 n m nm nm个元素
- 空间复杂度: O ( n m ) O(nm) O(nm)
- 时间复杂度:
O
(
n
m
l
o
g
(
n
m
)
)
O(nmlog(nm))
O(nmlog(nm)),其中
m
m
m是primes数组的长度
int nthSuperUglyNumber(int n, vector<int>& primes) {
// vector<int> ugly(n+2, 0);
int ugly = 1;
priority_queue<long, vector<long>, greater<long>> q;
int k = 1;
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: primes){
q.push(minn * fac);
}
}
// return ugly[n];
return ugly;
}
- 方法2:若一个数
x
x
x是超级丑数,则
p
r
i
m
e
s
[
0
]
×
x
primes[0]\times x
primes[0]×x、
p
i
m
e
s
[
1
]
×
x
pimes[1]\times x
pimes[1]×x、
p
r
i
m
e
s
[
2
]
×
x
primes[2] \times x
primes[2]×x…
p
r
i
m
e
s
[
m
−
1
]
×
x
primes[m-1] \times x
primes[m−1]×x都是超级丑数
- 得出m条链表:
*primes[0]: ugly[1]*primes[0] -> ugly[2]*primes[0] -> ugly[3]*primes[0] ...
*primes[1]: ugly[1]*primes[1] -> ugly[2]*primes[1] -> ugly[3]*primes[1]...
*primes[2]: ugly[1]*primes[2] -> ugly[2]*primes[2] -> ugly[3]*primes[2] ...
*primes[m-1]: ugly[1]*primes[m-1] -> ugly[2]*primes[m-1] -> ugly[3]*primes[m-1] ...
- 多路归并,参考k个链表的合并,需要用到优先队列
- 结构体node记录当前是哪条链表(fac),指针指向了第几个丑数(id),此时的丑数是多少(pro)
- 优先队列排序的是pro
- 当每次从优先队列取出最小元素时,判断是否重复,若不重复就是下一个丑数,将其放入丑数列表,然后该链表的指针指向往后移动
- 或当每次从优先队列取出最小元素时,将其视为下一个丑数放入丑数列表,并将该链表的指针指向往后移动;因为堆中可能存在重复元素(而由方法1的分析可得重复元素一定是相邻的),所以比较堆顶元素与该元素的大小,相同就弹出(并将弹出元素所在的链表的指针往后移动),直到不同为止
- 复杂度
- 时间复杂度:
O
(
m
a
x
(
n
l
o
g
m
,
m
l
o
g
m
)
)
O(max(nlogm, mlogm))
O(max(nlogm,mlogm)),其中
m
m
m是primes数组的长度
- 合并m个链表,堆中是每个链表的一个节点,每次取出放入元素
O
(
l
o
g
m
)
O(logm)
O(logm)
- 要找第n个丑数,所以需要n次,所以总共 O ( n l o g m ) O(nlogm) O(nlogm)
- 初始时将primes的m个元素入堆, O ( m l o g m ) O(mlogm) O(mlogm)
- 所以时间复杂度为二者中的较大者
- 合并m个链表,堆中是每个链表的一个节点,每次取出放入元素
O
(
l
o
g
m
)
O(logm)
O(logm)
- 空间复杂度:
O
(
n
+
m
)
O(n+m)
O(n+m)
- ugly数组 O ( n ) O(n) O(n)(必要的,需要指针定位),堆 O ( m ) O(m) O(m)
- 时间复杂度:
O
(
m
a
x
(
n
l
o
g
m
,
m
l
o
g
m
)
)
O(max(nlogm, mlogm))
O(max(nlogm,mlogm)),其中
m
m
m是primes数组的长度
- 结构体node记录当前是哪条链表(fac),指针指向了第几个丑数(id),此时的丑数是多少(pro)
- 得出m条链表:
int nthSuperUglyNumber(int n, vector<int>& primes){
vector<long> ugly(n+2, 0); //否则会overflow
priority_queue<node> q;
for(int fac: primes){
q.push(node{1ll, fac, 1});
}
int k = 1;
while(k <= n){
node t = q.top();
q.pop();
if(t.pro != ugly[k-1]) //1
ugly[k++] = t.pro;
q.push(node{ugly[t.id] * t.fac, t.fac, t.id+1});
// while(!q.empty() && q.top().pro == t.pro){//2。1、2择一写即可
// t = q.top();
// q.pop();
// 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;
}
};