0%

丑数

题目:

我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。

示例:

1
2
3
输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。

方法一:最小堆

要得到从小到大的第 n 个丑数,可以使用最小堆实现。
初始时堆为空。首先将最小的丑数 1 加入堆。
每次取出堆顶元素 x,则 x 是堆中最小的丑数,由于 2x, 3x, 5x 也是丑数,因此将 2x, 3x, 5x 加入堆。
上述做法会导致堆中出现重复元素的情况。为了避免重复元素,可以使用哈希集合去重,避免相同元素多次加入堆。
在排除重复元素的情况下,第 n 次从最小堆中取出的元素即为第 n 个丑数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public long nthUglyNumber2(int n) {
int[] factors = {2, 3, 5};
Set<Long> set = new HashSet<Long>();
PriorityQueue<Long> heap = new PriorityQueue<Long>();
set.add(1L);
heap.offer(1L);
long ugly = 0;
for (int i = 0; i < n; i++) {
ugly = heap.poll();
for (int factor : factors) {
long next = ugly * factor;
// set中有重复元素add方法返回false
if (set.add(next)) {
heap.offer(next);
}
}
}
return ugly;
}

复杂度分析

  • 时间复杂度:O(Nlog N)。得到第 n 个丑数需要进行 n 次循环,每次循环都要从最小堆中取出 1 个元素以及向最小堆中加入最多 3 个元素,时间复杂度是 C*O(log N),总的时间复杂度是 O(Nlog N)。
  • 空间复杂度:O(N)。主要取决于最小堆和哈希集合的大小,最小堆和哈希集合的大小都不会超过 3n。

方法二:动态规划

1
2
3
4
5
6
7
8
9
10
11
12
13
public int nthUglyNumber3(int n) {
int a = 0, b = 0, c = 0;
int[] dp = new int[n];
dp[0] = 1;
for (int i = 1; i < n; i++) {
int n2 = dp[a] * 2, n3 = dp[b] * 3, n5 = dp[c] * 5;
dp[i] = Math.min(Math.min(n2, n3), n5);
if (dp[i] == n2) a++;
if (dp[i] == n3) b++;
if (dp[i] == n5) c++;
}
return dp[n - 1];
}

复杂度分析

  • 时间复杂度 O(N) : 其中 N = n,动态规划需遍历计算 dp 列表。
  • 空间复杂度 O(N) : 长度为 N 的 dp 列表使用 O(N) 的额外空间。

题目链接

poj1338