题目介绍
力扣264题:leetcode-cn.com/problems/ug…
方法一:最小堆
要得到从小到大的第 nn 个丑数,可以使用最小堆实现。初始时堆为空。首先将最小的丑数 11 加入堆。
每次取出堆顶元素 x,则 x 是堆中最小的丑数,由于 2x, 3x, 5x2x,3x,5x 也是丑数,因此将 2x, 3x, 5x 加入堆。
上述做法会导致堆中出现重复元素的情况。为了避免重复元素,可以使用哈希集合去重,避免相同元素多次加入堆。在排除重复元素的情况下,第 n 次从最小堆中取出的元素即为第 n 个丑数。
代码如下:
class Solution {
public int nthUglyNumber(int n) {
int[] factors = {2, 3, 5};
Set<Long> seen = new HashSet<Long>();
PriorityQueue<Long> heap = new PriorityQueue<Long>();
seen.add(1L);
heap.offer(1L);
int ugly = 0;
for (int i = 0; i < n; i++) {
//弹出当前最小丑数
long curr = heap.poll();
ugly = (int) curr;
for (int factor : factors) {
long next = curr * factor;
if (seen.add(next)) {
heap.offer(next);
}
}
}
return ugly;
}
}
复杂度分析
-
时间复杂度:O(nlogn)。得到第n 个丑数需要进行 n 次循环,每次循环都要从最小堆中取出 1 个元素以及向最小堆中加入最多 3 个元素,因此每次循环的时间复杂度是 O(log3n+3log3n)=O(logn),总时间复杂度是 O(nlogn)。
-
空间复杂度:O(n)。空间复杂度主要取决于最小堆和哈希集合的大小,最小堆和哈希集合的大小都不会超过 3n。
方法二:动态规划
该思路与[313. 超级丑数]的解题思路类似, 只需要把代码稍加修改即可,代码如下:
class Solution {
public int nthUglyNumber(int n) {
int[] primes = new int[]{2,3,5};
int pLen = 3;
int[] indexes = new int[pLen];
int[] dp = new int[n];
dp[0] = 1;
for (int i = 1; i < n; i++) {
// 因为选最小值,先假设一个最大值
dp[i] = Integer.MAX_VALUE;
for (int j = 0; j < pLen; j++) {
dp[i] = Math.min(dp[i], dp[indexes[j]] * primes[j]);
}
// dp[i] 是之前的哪个丑数乘以对应的 primes[j] 选出来的,给它加 1
for (int j = 0; j < pLen; j++) {
if (dp[i] == dp[indexes[j]] * primes[j]) {
// 注意:这里不止执行一次,例如选出 14 的时候,2 和 7 对应的最小丑数下标都要加 1,大家可以打印 indexes 和 dp 的值加以验证
indexes[j]++;
}
}
}
return dp[n - 1];
}
}
不过上述代码还是比较复杂,由于质数数组只有2,3,5,思路还可以这样:
定义数组 dp,其中 dp[i] 表示第 i 个丑数,第 n 个丑数即为 dp[n]。
由于最小的丑数是 1,因此 dp[1]=1。
如何得到其余的丑数呢?定义三个指针 p2,p3,p5,表示下一个丑数是当前指针指向的丑数乘以对应的质因数。初始时,三个指针的值都是 1。
当 2 ≤ i ≤n 时,令 dp[i] = min(dp[p2] × 2,dp[p3] × 3,dp[p5] × 5)
,然后分别比较dp[i]
和 dp[p2]×2,dp[p3]×3,dp[p5]×5
是否相等,如果相等则将对应的指针加 1。
代码如下:
class Solution {
public int nthUglyNumber(int n) {
int[] dp = new int[n + 1];
dp[1] = 1;
int p2 = 1, p3 = 1, p5 = 1;
for (int i = 2; i <= n; i++) {
int num2 = dp[p2] * 2, num3 = dp[p3] * 3, num5 = dp[p5] * 5;
dp[i] = Math.min(Math.min(num2, num3), num5);
if (dp[i] == num2) {
p2++;
}
if (dp[i] == num3) {
p3++;
}
if (dp[i] == num5) {
p5++;
}
}
return dp[n];
}
}
复杂度分析
-
时间复杂度:O(n)。需要计算数组 dp 中的 n 个元素,每个元素的计算都可以在 O(1) 的时间内完成。
-
空间复杂度:O(n)。空间复杂度主要取决于数组 dp 的大小。