Medium
题目描述
丑数是只包含质因数 2、3 和 5 的正整数。
给定一个整数 n,返回第 n 个丑数。
示例 1:
输入:n = 10
输出:12
解释:[1, 2, 3, 4, 5, 6, 8, 9, 10, 12] 是前 10 个丑数的序列。
示例 2:
输入:n = 1
输出:1
解释:1 没有质因数,因此它的所有质因数都限制在 2、3 和 5 以内。
约束条件:
- 1 <= n <= 1690
提示:
- 朴素方法是对每个数字调用 isUgly,直到找到第 n 个。大部分数字都不是丑数。尝试专注于只生成丑数。
- 丑数必须由较小的丑数乘以 2、3 或 5 得到。
- 关键是如何维持丑数的顺序。尝试类似于合并三个有序列表的方法:L₁、L₂ 和 L₃。
- 假设你有第 k 个丑数 Uₖ,那么 Uₖ₊₁ 必须是 Min(L₁ * 2, L₂ * 3, L₃ * 5)。
解题思路
解题思路
这道题有两种主要解法:三指针动态规划(推荐)和最小堆。
方法一:三指针动态规划(推荐)
核心思想是:每个丑数都可以由之前的丑数乘以 2、3 或 5 得到。我们维护三个指针分别指向可以乘以 2、3、5 的丑数位置。
算法流程:
- 初始化 dp 数组,dp[0] = 1(第一个丑数)
- 维护三个指针 p2、p3、p5,分别表示下一个要乘以 2、3、5 的丑数的索引
- 每次计算 dp[p2] * 2、dp[p3] * 3、dp[p5] * 5 的最小值作为下一个丑数
- 更新对应的指针(注意可能有重复值,需要同时更新多个指针)
方法二:最小堆
使用最小堆存储所有可能的丑数,每次取出最小值,然后将其乘以 2、3、5 后加入堆中。需要用集合去重。
三指针方法时间复杂度更优,且空间使用更少,是推荐解法。
代码实现
class Solution {
public:
int nthUglyNumber(int n) {
vector<int> dp(n);
dp[0] = 1;
int p2 = 0, p3 = 0, p5 = 0;
for (int i = 1; i < n; i++) {
int next2 = dp[p2] * 2;
int next3 = dp[p3] * 3;
int next5 = dp[p5] * 5;
dp[i] = min({next2, next3, next5});
if (dp[i] == next2) p2++;
if (dp[i] == next3) p3++;
if (dp[i] == next5) p5++;
}
return dp[n - 1];
}
};
class Solution:
def nthUglyNumber(self, n: int) -> int:
dp = [0] * n
dp[0] = 1
p2 = p3 = p5 = 0
for i in range(1, n):
next2 = dp[p2] * 2
next3 = dp[p3] * 3
next5 = dp[p5] * 5
dp[i] = min(next2, next3, next5)
if dp[i] == next2:
p2 += 1
if dp[i] == next3:
p3 += 1
if dp[i] == next5:
p5 += 1
return dp[n - 1]
public class Solution {
public int NthUglyNumber(int n) {
int[] dp = new int[n];
dp[0] = 1;
int p2 = 0, p3 = 0, p5 = 0;
for (int i = 1; i < n; i++) {
int next2 = dp[p2] * 2;
int next3 = dp[p3] * 3;
int next5 = dp[p5] * 5;
dp[i] = Math.Min(Math.Min(next2, next3), next5);
if (dp[i] == next2) p2++;
if (dp[i] == next3) p3++;
if (dp[i] == next5) p5++;
}
return dp[n - 1];
}
}
var nthUglyNumber = function(n) {
const dp = new Array(n);
dp[0] = 1;
let p2 = 0, p3 = 0, p5 = 0;
for (let i = 1; i < n; i++) {
const next2 = dp[p2] * 2;
const next3 = dp[p3] * 3;
const next5 = dp[p5] * 5;
dp[i] = Math.min(next2, next3, next5);
if (dp[i]
复杂度分析
| 复杂度类型 | 三指针动态规划 | 最小堆方法 |
|---|---|---|
| 时间复杂度 | O(n) | O(n log n) |
| 空间复杂度 | O(n) | O(n) |
相关题目
. Merge k Sorted Lists (Hard)
. Count Primes (Medium)
. Ugly Number (Easy)
. Perfect Squares (Medium)
. Super Ugly Number (Medium)
. Ugly Number III (Medium)