线性动态规划:丑数 II
题目描述
给你一个整数 n,请你找出并返回第 n 个 丑数。
丑数 是指只包含质因数 2、3 和/或 5 的正整数。例如,1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。
解题过程
-
理解问题与定义状态
我们需要生成一个丑数序列,其中每个数都是由前面的丑数乘以 2、3 或 5 得到的(1 是第一个丑数)。关键在于如何按顺序生成它们,避免遗漏或重复。
我们可以使用动态规划,定义一个数组dp,其中dp[i]表示第 i 个丑数(i 从 1 开始计数)。因此,我们的目标就是计算dp[n]。
初始状态:dp[1] = 1,因为 1 是第一个丑数。 -
关键思路:三指针法
单纯地逐个判断每个数是否是丑数效率很低。一个高效的方法是使用三个指针(或索引)p2,p3,p5。p2指向下一个将要乘以 2 的丑数。p3指向下一个将要乘以 3 的丑数。p5指向下一个将要乘以 5 的丑数。
在每一步中,我们计算:
num2 = dp[p2] * 2
num3 = dp[p3] * 3
num5 = dp[p5] * 5
然后,下一个丑数就是这三个数中的最小值。这样我们就能保证丑数序列是严格递增的。
-
状态转移方程
设当前要计算dp[i]。- 计算候选值:
candidate2 = dp[p2] * 2
candidate3 = dp[p3] * 3
candidate5 = dp[p5] * 5 dp[i] = min(candidate2, candidate3, candidate5)- 更新指针:如果
dp[i]等于某个候选值,那么对应的指针就向后移动一位(p2++,p3++或p5++)。注意:如果dp[i]同时等于多个候选值(例如 6 = 23 = 32),那么这些对应的指针都需要移动,以避免在后续序列中产生重复的丑数。
- 计算候选值:
-
逐步计算示例(以 n=10 为例)
初始化:dp[1] = 1,p2 = 1,p3 = 1,p5 = 1-
i=2:
num2 = dp[1]*2 = 2
num3 = dp[1]*3 = 3
num5 = dp[1]*5 = 5
min(2,3,5) = 2->dp[2] = 2
因为 2 来自num2,所以p2移动到 2 (p2++)。p3和p5不变。
序列: [1, 2] -
i=3:
num2 = dp[2]*2 = 4
num3 = dp[1]*3 = 3
num5 = dp[1]*5 = 5
min(4,3,5) = 3->dp[3] = 3
因为 3 来自num3,所以p3移动到 2 (p3++)。p2和p5不变。
序列: [1, 2, 3] -
i=4:
num2 = dp[2]*2 = 4
num3 = dp[2]*3 = 6
num5 = dp[1]*5 = 5
min(4,6,5) = 4->dp[4] = 4
因为 4 来自num2,所以p2移动到 3 (p2++)。p3和p5不变。
序列: [1, 2, 3, 4] -
i=5:
num2 = dp[3]*2 = 6
num3 = dp[2]*3 = 6
num5 = dp[1]*5 = 5
min(6,6,5) = 5->dp[5] = 5
因为 5 来自num5,所以p5移动到 2 (p5++)。p2和p3不变。
序列: [1, 2, 3, 4, 5] -
i=6:
num2 = dp[3]*2 = 6
num3 = dp[2]*3 = 6
num5 = dp[2]*5 = 10
min(6,6,10) = 6
注意,6 同时等于num2和num3。
->dp[6] = 6
因为 6 来自num2和num3,所以p2移动到 4 (p2++),p3移动到 3 (p3++)。p5不变。
序列: [1, 2, 3, 4, 5, 6] -
i=7:
num2 = dp[4]*2 = 8
num3 = dp[3]*3 = 9
num5 = dp[2]*5 = 10
min(8,9,10) = 8->dp[7] = 8
p2移动到 5 (p2++)
序列: [1, 2, 3, 4, 5, 6, 8] -
i=8:
num2 = dp[5]*2 = 10
num3 = dp[3]*3 = 9
num5 = dp[2]*5 = 10
min(10,9,10) = 9->dp[8] = 9
p3移动到 4 (p3++)
序列: [1, 2, 3, 4, 5, 6, 8, 9] -
i=9:
num2 = dp[5]*2 = 10
num3 = dp[4]*3 = 12
num5 = dp[2]*5 = 10
min(10,12,10) = 10
注意,10 同时等于num2和num5。
->dp[9] = 10
p2移动到 6 (p2++),p5移动到 3 (p5++)
序列: [1, 2, 3, 4, 5, 6, 8, 9, 10] -
i=10:
num2 = dp[6]*2 = 12
num3 = dp[4]*3 = 12
num5 = dp[3]*5 = 15
min(12,12,15) = 12->dp[10] = 12
p2移动到 7 (p2++),p3移动到 5 (p3++)
序列: [1, 2, 3, 4, 5, 6, 8, 9, 10, 12]
因此,第 10 个丑数是 12。
-
-
算法总结
- 初始化动态规划数组
dp,大小为 n+1,dp[1] = 1。 - 初始化三个指针
p2 = 1,p3 = 1,p5 = 1。 - 从
i = 2循环到i = n:
a. 计算num2 = dp[p2] * 2,num3 = dp[p3] * 3,num5 = dp[p5] * 5。
b.dp[i] = min(num2, num3, num5)。
c. 如果dp[i] == num2,则p2++。
如果dp[i] == num3,则p3++。
如果dp[i] == num5,则p5++。 - 返回
dp[n]。
- 初始化动态规划数组
这个方法的时间复杂度是 O(n),空间复杂度也是 O(n),用于存储丑数序列。它高效地按顺序生成了所有丑数,避免了重复计算和排序。