目录
引言
动态规划(Dynamic Programming, 简称DP)是一种在数学、计算机科学、经济学和生物信息学等领域广泛使用的算法设计技术。它通过把原问题分解为相对简单的子问题的方式,来求解复杂问题。动态规划的核心思想包括两个方面:
最优子结构
一个问题的最优解包含其子问题的最优解,即可以通过组合子问题的最优解来构造原问题的最优解。
例如:假设你有一个装满不同面额硬币的钱包,包括1元、5元、10元等,你需要找给顾客正好30元的零钱,且希望使用的硬币数量尽可能少。
这个问题可以分解为多个子问题:
- 找给顾客1元:显然只需要一个1元硬币。
- 找给顾客5元:显然只需要一个5元硬币。
- 找给顾客10元:显然只需要一个10元硬币。
- 找给顾客20元:这里可能需要多个硬币,比如两个10元,或者一个10元加上两个5元,或者四个5元,等等,但我们要找的是使用硬币数量最少的那种方式。
- 找给顾客30元:这就是我们的原问题。它可以分解为找给顾客20元的问题(子问题)加上找给顾客10元的问题(另一个子问题),或者找给顾客25元(比如5个5元)加上5元,等等。我们需要找到这些组合中硬币数量最少的那一种。
如果我们知道了找给顾客任意小于或等于30元金额的最少硬币数量(即所有子问题的最优解),那么我们就可以通过组合这些最优解来找到找给顾客30元的最少硬币数量(即原问题的最优解)。
实际上在解决这个具体的零钱找零问题时,我们可能会使用动态规划的一个变种——贪心算法
重叠子问题
在递归算法中,相同的子问题会被多次计算。动态规划通过存储这些子问题的解来避免重复计算,从而提高效率
例如:斐波那契数列:0, 1, 1, 2, 3, 5, 8, 13, 21, ...,其中每个数字是前两个数字的和。
使用递归方法来计算斐波那契数列的第n
项,在计算fib(4)
时,会分别计算fib(3)
和fib(2)
,而在计算fib(3)
时又会计算fib(2)
和fib(1)
。随着n
的增大,重复计算的量会急剧增加。
def fib(n):
if n <= 1:
return n
return fib(n-1) + fib(n-2)
使用一个数组来保存已计算过的斐波那契数来避免重复计算,从而大大提高效率。
def fib_dp(n):
if n <= 1:
return n
dp = [0] * (n + 1)
dp[1] = 1
for i in range(2, n + 1):
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
打家劫舍(LeetCode 198题)
动态规划通常用于解决具有重叠子问题和最优子结构性质的问题。它使用一个数组或字典来存储不同状态的解,并通过状态转移方程来定义如何从一个或多个已知状态推导出下一个状态。
通过一个简单的例子——打家劫舍”(L