题目描述
我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。
解题思路
丑数的递推性质: 丑数只包含因子 2 , 3 , 5 2, 3, 5 2,3,5 ,因此有 “丑数 = = = 某较小丑数 × \times × 某因子” (例如: 10 = 5 × 2 10 = 5 \times 2 10=5×2)。
设已知长度为
n
n
n 的丑数序列
x
1
,
x
2
,
⋯
,
x
n
x_1, x_2, \cdots , x_n
x1,x2,⋯,xn,求第
n
+
1
n+1
n+1 个丑数
x
n
+
1
x_{n+1}
xn+1 。根据递推性质,丑数
x
n
+
1
x_{n+1}
xn+1 只可能是以下三种情况其中之一(索引
a
,
b
,
c
a, b, c
a,b,c 为未知数):
丑数递推公式: 若索引
a
,
b
,
c
a,b,c
a,b,c 满足以上条件,则下个丑数
x
n
+
1
x_{n+1}
xn+1 为以下三种情况中的 最小值 ;
x
n
+
1
=
m
i
n
(
x
a
×
2
,
x
b
×
3
,
x
c
×
5
)
x_n+1=min(x_a×2,x_b×3,x_c×5)
xn+1=min(xa×2,xb×3,xc×5)
由于
x
n
+
1
x_{n+1}
xn+1 是 最接近
x
n
x_n
xn 的丑数,因此索引
a
,
b
,
c
a, b, c
a,b,c 需满足以下条件:
因此,可设置指针
a
,
b
,
c
a,b,c
a,b,c 指向首个丑数(即
1
1
1 ),循环根据递推公式得到下个丑数,并每轮将对应指针执行
+
1
+1
+1 即可。
动态规划解析:
-
状态定义: 设动态规划列表 d p dp dp , d p [ i ] dp[i] dp[i] 代表第 i + 1 i + 1 i+1 个丑数;
-
转移方程:
1.当索引 a , b , c a, b, c a,b,c 满足以下条件时, d p [ i ] dp[i] dp[i] 为三种情况的最小值;
2.每轮计算 d p [ i ] dp[i] dp[i] 后,需要更新索引 a , b , c a, b, c a,b,c 的值,使其始终满足方程条件。实现方法:
分别独立判断 d p [ i ] dp[i] dp[i] 和 d p [ a ] × 2 dp[a] \times 2 dp[a]×2 , d p [ b ] × 3 dp[b] \times 3 dp[b]×3 , d p [ c ] × 5 dp[c] \times 5 dp[c]×5 的大小关系,若相等则将对应索引 a a a , b b b , c c c 加 1 1 1 ;
-
初始状态: d p [ 0 ] = 1 dp[0] = 1 dp[0]=1 ,即第一个丑数为 1 1 1 ;
-
返回值: d p [ n − 1 ] dp[n-1] dp[n−1] ,即返回第 n n n 个丑数;
复杂度分析
- 时间复杂度 O ( N ) O(N) O(N) : 其中 N = n N = n N=n ,动态规划需遍历计算 d p dp dp 列表。
- 空间复杂度 O ( N ) O(N) O(N) : 长度为 N N N 的 d p dp dp 列表使用 O ( N ) O(N) O(N) 的额外空间。
C++代码实现
class Solution {
public:
int nthUglyNumber(int n) {
int a = 0, b = 0, c = 0;
int dp[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] = min(min(n2, n3), n5);
if(dp[i] == n2) a++;
if(dp[i] == n3) b++;
if(dp[i] == n5) c++;
}
return dp[n - 1];
}
};