返回

再笨的人都能学会的动态规划(一)

前端

动态规划,顾名思义,就是将一个大问题分解成一系列小问题,逐个求解,并将这些小问题的解作为大问题的子问题,最终得到大问题的解。 动态规划的核心思想是:

    1. 最优子结构 :大问题的最优解可以由其子问题的最优解组合而成。
    1. 无后效性 :子问题的最优解不受其后续决策的影响。

动态规划的适用场景:

    1. 问题可以分解成一系列相互关联的子问题。
    1. 子问题的最优解可以由其子问题的最优解组合而成。
    1. 子问题的最优解不受其后续决策的影响。

动态规划的基本步骤:

    1. 将大问题分解成一系列相互关联的子问题。
    1. 为每个子问题定义状态和决策。
    1. 使用动态规划方程计算每个子问题的最优解。
    1. 将子问题的最优解组合成大问题的最优解。

下面,我们以剑指 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

总结:

动态规划是一种强大的算法,可以用来解决许多复杂的问题。其基本思想是将大问题分解成一系列相互关联的子问题,逐个求解,并将这些子问题的解作为大问题的子问题,最终得到大问题的解。动态规划的适用场景非常广泛,只要满足最优子结构和无后效性的条件,就可以使用动态规划来解决。