返回
思维交锋 | 动态规划解丑数 II:庖丁解牛式拆解
后端
2023-12-21 18:54:37
丑数 II:庖丁解牛式拆解
踏上算法修行之路,终将遇到难题。而丑数 II 就是一道考验你算法思维的试金石。它要求你找出第 n 个丑数。丑数即只包含质因数 2、3 和/或 5 的正整数。
面对这样一个看起来错综复杂的题目,我们可以借鉴庖丁解牛的智慧,将问题拆解成更易解决的子问题。动态规划正是这种拆解思想的完美实践。
动态规划:化繁为简的艺术
动态规划是一种解决问题的策略,其核心思想是将大问题拆解成一系列更易解决的子问题,然后从子问题入手,逐个解决,最终组合得到大问题的解。
我们把每一个子问题的解存储起来,以便当遇到相同的子问题时,可以快速地检索到解,避免重复计算。
动态规划具有两个核心特征:子问题最优性和重复子问题。子问题最优性意味着子问题的解可以用来得到原问题的最优解。而重复子问题是指子问题在求解过程中被多次遇到。
丑数 II 中的动态规划
对于丑数 II,我们可以定义子问题为:求第 n 个丑数。而动态规划的难点在于找到恰当的子问题。
丑数的性质为:每一个丑数都是由一个更小的丑数乘以 2、3 或 5 得到的。因此,我们可以将求解第 n 个丑数的问题分解成三个子问题:
- 第 n 个丑数是第 n-1 个丑数乘以 2 得到的。
- 第 n 个丑数是第 n-1 个丑数乘以 3 得到的。
- 第 n 个丑数是第 n-1 个丑数乘以 5 得到的。
我们只需要比较这三个子问题的结果,就能得到第 n 个丑数。
实现步骤
- 创建一个数组 dp 来存储丑数,dp[i] 表示第 i 个丑数。
- 初始化 dp[0] = 1,因为 1 是第一个丑数。
- 使用三个指针 p2、p3、p5 分别指向 dp 数组中的位置,这些位置分别存储着乘以 2、3 和 5 后的最小丑数。
- 对于 i 从 1 开始循环到 n,执行以下步骤:
- 计算三个子问题的解:dp[i] = dp[p2] * 2、dp[i] = dp[p3] * 3 和 dp[i] = dp[p5] * 5。
- 找到三个子问题的最小解,并将其存储在 dp[i] 中。
- 更新指针 p2、p3 和 p5,使其指向 dp 数组中分别存储着乘以 2、3 和 5 后的最小丑数的位置。
代码实现
def nthUglyNumber(n):
"""
:type n: int
:rtype: int
"""
if n <= 0:
return 0
# 创建一个数组 dp 来存储丑数
dp = [0] * n
# 初始化 dp[0] = 1
dp[0] = 1
# 创建三个指针 p2、p3、p5 分别指向 dp 数组中的位置
p2 = 0
p3 = 0
p5 = 0
# 循环从 1 开始到 n
for i in range(1, n):
# 计算三个子问题的解
num2 = dp[p2] * 2
num3 = dp[p3] * 3
num5 = dp[p5] * 5
# 找到三个子问题的最小解
dp[i] = min(num2, num3, num5)
# 更新指针 p2、p3 和 p5
if dp[i] == num2:
p2 += 1
if dp[i] == num3:
p3 += 1
if dp[i] == num5:
p5 += 1
# 返回第 n 个丑数
return dp[n-1]
结语
通过动态规划,我们成功地将丑数 II 问题分解成更易解决的子问题,并最终得到了问题的解。这种自底向上的思维方式,正是动态规划的魅力所在。
动态规划是一种强大的算法,可以用来解决很多复杂的问题。希望这篇文章能为你打开动态规划的大门,让你在算法的海洋中乘风破浪!