题目链接:
不想戳的看下图:
解题思路1:
用题目给的例子 [2,7,13,19] 来说。
将 [2,7,13,19] 依次入堆。
出堆一个数字,也就是 2。这时取到了第一个超级丑数。
接着将 2 和 [2,7,13,19] 的乘积,也就是 [4,14,26,38] 依次入堆。这样就可以得到其他的丑数。
每次堆都可以取到最小的,每次我们也会将最小的从堆中移除。因此取 n 次自然就是第 n 大的超级丑数了。
注:
堆的解法没有太大难度,唯一需要注意的是去重。比如 2 * 13 = 26,而 13 * 2 也是 26。
去重可以每次拿堆顶元素和先前出堆的数比较,一样则弹出堆。
代码如下:
class Solution {
public int nthSuperUglyNumber(int n, int[] primes) {
// 优先队列(底层实现为二叉堆)
PriorityQueue<Long> queue = new PriorityQueue<>();
// 统计是第几个丑数
int count = 1;
// 返回的结果
long res = 1;
queue.add(res);
while (count <= n) {
res = queue.poll();
// 将优先队列中的和res一样的数去重,避免重复统计相同的丑数
while (!queue.isEmpty() && res == queue.peek()) {
queue.poll();
}
count++;
// 将此时的最小丑数与所有质数相乘,再加入新的优先队列中
for (int i = 0; i < primes.length; i++) {
queue.offer(res * primes[i]);
}
}
return (int )res;
}
}
这种思路当然可以,但时间和空间效率较低。
于是我们有下一种解法:。
解题思路2:
我们用到指针。
为什么会想到指针?
1.丑数简单来说就是质因数的乘积,因为是正序排序的丑数,后面的丑数一定是由前面已得出的丑数乘上某个质因数。
2.由于正序的束缚,我们需要这个乘积是这k个质因数与各自前面已得出的丑数的k个乘积中最小的。
3.很自然的我们就可以联想到要储存这k个质因数对应的前面已得出的丑数,而这些前面已得出的丑数是由dp数组内的索引表示的。
我们利用一个指针数组points来储存和更新这k个质因数对应的前面已得出的丑数的dp索引。
为了计算方便,我们同时可以设置一个备选丑数数组val来储存和更新当前所有备选的丑数,即k个质因数与对应前面得出的丑数的乘积。
而其中最小的那个备选项就是我们需要的下一个丑数。
状态转移方程:dp[i] = min(dp[points[k]] * primes[k])
代码如下:
class Solution {
public int nthSuperUglyNumber(int n, int[] primes) {
int k=primes.length;
int[] dp=new int[n+1],points=new int[k],val=new int[k];
Arrays.fill(points,1);
dp[1]=1;
for(int i=2;i<=n;i++) {
int min=Integer.MAX_VALUE;
for(int j=0;j<k;j++) {
val[j]=dp[points[j]]*primes[j];
min=Math.min(min,val[j]);
}
dp[i]=min;
for(int j=0;j<k;j++) {
if(min==val[j]) {
points[j]++;
}
}
}
return dp[n];
}
}
小结
本题为第264题的扩展,但核心思路都是相同的,即指针的巧用。只要学会这种方法,同类题都能迎刃而解!