LeetCode 313. 超级丑数
题目描述


题意分析
超级丑数是所有质因子都来自
primes的正整数。按数值从小到大排列、相同数值只保留一次,求第n项。1 没有质因子,也满足条件;题目保证所求结果在 32 位有符号整数范围内。
解法:多指针 DP
核心思路
[!blue]
用
dp[i]保存第i + 1个不同的超级丑数,初始dp[0] = 1。对每个质数primes[j],依次将它乘以dp[0]、dp[1]……,会得到一条严格递增的候选序列。用idx[j]记录该序列下一个尚未输出的乘法位置。每轮比较所有
dp[idx[j]] * primes[j],取最小值作为下一项。任何大于 1 的超级丑数都可以除去一个列表中的质因子,得到更小的超级丑数;当它成为尚未输出的最小值时,这个较小因子一定已经在dp中,因此它一定出现在某条候选序列里。取所有序列当前头部的最小值,不会漏掉正确下一项。同一个数可能由不同质数相乘得到。选中最小值后,必须把所有乘积等于它的
idx[j]都加一,使每条序列的下一候选都严格大于刚输出的值。没有命中的序列保持不动,否则会跳过尚未使用的候选。这样直接合并出了递增且不重复的结果,无需额外去重集合。先把新结果写入
dp[i],再推进指针,后续乘法就能使用这项历史结果。虽然最终答案保证不溢出 32 位整数,未被选中的候选乘积仍可能更大,因此 Java 用long、Go 用int64完成乘法和比较,再把选中的值保存到结果数组。
解题步骤
- 创建长度为
n的dp,令dp[0] = 1,每个质数的下标都初始化为 0。- 从
i = 1开始,扫描全部质数,求当前候选乘积的最小值。- 将最小值写入
dp[i],再扫描一次,推进所有命中该值的指针。- 生成
n项后返回dp[n - 1];n = 1时直接得到初始值 1。
代码实现
class Solution {
public int nthSuperUglyNumber(int n, int[] primes) {
int k = primes.length;
int[] idx = new int[k];
int[] dp = new int[n];
dp[0] = 1;
for (int i = 1; i < n; i++) {
// 每条流的当前乘积都是该流尚未取出的最小候选
long min = Long.MAX_VALUE;
for (int j = 0; j < k; j++) {
min = Math.min(min, (long) dp[idx[j]] * primes[j]);
}
dp[i] = (int) min;
for (int j = 0; j < k; j++) {
// 选中值已写入,所有相等候选一起推进以去重
if ((long) dp[idx[j]] * primes[j] == min) {
idx[j]++;
}
}
}
return dp[n - 1];
}
}
func nthSuperUglyNumber(n int, primes []int) int {
k := len(primes)
idx := make([]int, k)
dp := make([]int, n)
dp[0] = 1
for i := 1; i < n; i++ {
// 每条流的当前乘积都是该流尚未取出的最小候选
min := int64(1<<63 - 1)
for j := 0; j < k; j++ {
v := int64(dp[idx[j]]) * int64(primes[j])
if v < min {
min = v
}
}
dp[i] = int(min)
for j := 0; j < k; j++ {
// 选中值已写入,所有相等候选一起推进以去重
if int64(dp[idx[j]])*int64(primes[j]) == min {
idx[j]++
}
}
}
return dp[n-1]
}
复杂度分析
- 时间复杂度:$O(nk)$,
k为质数个数,每项扫描k条候选序列两次。- 空间复杂度:$O(n+k)$,保存已生成序列和各流下标。
关键点总结
[!green]
dp同时保存输出历史,并为各质数的候选乘法提供因子。- 指针指向各自序列的首个未输出值,命中最小值的全部指针必须同步推进。
易错点总结
[!yellow]
- 只推进一个并列流,下一轮会再次输出同一值。
- 首项保持零,所有候选都会停在零。
- 只检查较小质数的流,会漏掉其他流更小的当前候选。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 264. 丑数 II | 中等 | 把固定三种质因子推广到给定集合,仍需维护各因子候选并同时跳过重复值。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!