返回
再笨的人都能学会的动态规划(一)
前端
2023-10-16 02:01:47
动态规划,顾名思义,就是将一个大问题分解成一系列小问题,逐个求解,并将这些小问题的解作为大问题的子问题,最终得到大问题的解。 动态规划的核心思想是:
-
- 最优子结构 :大问题的最优解可以由其子问题的最优解组合而成。
-
- 无后效性 :子问题的最优解不受其后续决策的影响。
动态规划的适用场景:
-
- 问题可以分解成一系列相互关联的子问题。
-
- 子问题的最优解可以由其子问题的最优解组合而成。
-
- 子问题的最优解不受其后续决策的影响。
动态规划的基本步骤:
-
- 将大问题分解成一系列相互关联的子问题。
-
- 为每个子问题定义状态和决策。
-
- 使用动态规划方程计算每个子问题的最优解。
-
- 将子问题的最优解组合成大问题的最优解。
下面,我们以剑指 Offer II 103. 最少的硬币数目为例,来讲解动态规划的具体应用:
题目
给定一组硬币的面值 coins 和一个总金额 amount,求出组成该总金额的最小硬币数目。如果无法组成该总金额,则返回 -1。
示例一:
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
示例二:
输入:coins = [2], amount = 3
输出:-1
解释:无法组成总金额为 3 的硬币组合
错误示范:
有的同学可能第一直觉就是,用贪婪算法来解决这个问题。即每次选择面值最大的硬币,直到凑够总金额。但是,这种方法并不总是能得到最优解。
正确的解法:
我们可以使用动态规划来解决这个问题。我们定义状态 dp[i] 为凑够金额 i 所需的最小硬币数目。
状态转移方程:
dp[i] = min(dp[i], dp[i - coin] + 1)
其中,coin 为硬币的面值。
初始化:
dp[0] = 0
计算过程:
for i = 1 to amount:
for coin in coins:
if i - coin >= 0:
dp[i] = min(dp[i], dp[i - coin] + 1)
最终结果:
dp[amount]
时间复杂度:
O(amount * coins)
空间复杂度:
O(amount)
代码实现:
def coinChange(coins, amount):
dp = [float('inf')] * (amount + 1)
dp[0] = 0
for i in range(1, amount + 1):
for coin in coins:
if i - coin >= 0:
dp[i] = min(dp[i], dp[i - coin] + 1)
return dp[amount] if dp[amount] != float('inf') else -1
总结:
动态规划是一种强大的算法,可以用来解决许多复杂的问题。其基本思想是将大问题分解成一系列相互关联的子问题,逐个求解,并将这些子问题的解作为大问题的子问题,最终得到大问题的解。动态规划的适用场景非常广泛,只要满足最优子结构和无后效性的条件,就可以使用动态规划来解决。