题目
我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。
示例:
输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。
说明:1 是丑数。
n 不超过1690。
思路
本题仍有递推规律可循,因此可用动态规划来求解:
第1个丑数: 1
第2个丑数:min(1*2, 1*3, 1*5) = 1*2=2
第3个丑数: min(2*2, 1*3, 1*5) = 1*3 = 3
对于*2的数而言,只能是2*2,因为1*2已经用过了;
对于*3的数而言,只能是1*3,因为1*3还没有用,它一定小于2*3;
对于*5的数而言,只能是1*5, 因为1*5还没有用,它一定小于2*5
第4个丑数: min(2*2, 2*3, 1*5) = 2*2 = 4
对于*2的数而言,只能是2*2,因为1*2已经用过了, 2*2还没用;
对于*3的数而言,只能是2*3,因为1*3已经用过了,2*3还没用;
对于*5的数而言,只能是1*5, 因为1*5还没有用,它一定小于2*5
... ...
由此可得递推公式:
res[n] = min(res[two]*2, res[three]*3, res[five]*5)
其中, 刚开始时two = trhree = five = 0.
后来,每被min选中1次,对应的索引就加1.
比如,第2个丑数是乘的2,则two += 1 ;
第3个丑数是乘的3,则three += 1 ;
依次类推,即可完成代码。
代码
按照上述思路,可以得到如下代码:
class Solution:
def nthUglyNumber(self, n: int) -> int:
two = 0
three = 0
five = 0
res = [1] * n
for i in range(1,n):
# 选择two
if (res[two]*2 < res[three]*3) and (res[two]*2 < res[five]*5):
res[i] = res[two]*2
two = two + 1
# 选择three
elif (res[three]*3 < res[two]*2) and (res[three]*3 < res[five]*5):
res[i] = res[three]*3
three = three + 1
# 选择five
elif (res[five]*5 < res[two]*2) and (res[five]*5 < res[three]*3):
res[i] = res[five]*5
five = five + 1
# two = three
elif (res[two]*2 == res[three]*3) and (res[two]*2 < res[five]*5):
res[i] = res[two]*2
two = two + 1
three = three + 1
# two = five
elif (res[two]*2 == res[five]*5) and (res[two]*2 < res[three]*3):
res[i] = res[two]*2
two = two + 1
five = five + 1
# three = five
elif (res[three]*3 == res[five]*5) and (res[three]*3 < res[two]*2):
res[i] = res[five]*5
three = three + 1
five = five + 1
# all equal
else:
res[i] = res[five]*5
two = two + 1
three = three + 1
five = five + 1
return res[-1]
上述代码没有使用min是因为python的min无法返回索引,所以不能判断是2,是3,还是5。但这是可以通过得到结果后再挨个去比来判断的,因此代码可以简化如下:
class Solution:
def nthUglyNumber(self, n: int) -> int:
two = 0
three = 0
five = 0
res = [1] * n
for i in range(1,n):
res[i] = min(res[two]*2, res[three]*3, res[five]*5)
if res[i] == res[two]*2:
two = two + 1
if res[i] == res[three]*3:
three = three + 1
if res[i] == res[five]*5:
five = five + 1
return res[-1]
复杂度分析
时间复杂度:上述两段代码时间复杂度相同,均为O(N);
空间复杂度:上述两段代码空间复杂度相同,均为O(N);
总结
动态规划可以通过从简单情况向后推导的方法来找规律。