本页记录了我们在代码库中用于解决各种算法问题的动态规划方法和技术。动态规划是一种解决优化问题的方法,通过将问题分解为更简单的子问题,并利用这些子问题的解来构建原问题的解。
有关动态规划中使用的二叉树等特定数据结构的更多信息,请参阅 树与图。
动态规划是一种借用自其他领域的思想。它涉及将问题分解为阶段,在这些阶段之间进行转换以达到目标。由于每一步通常有多个可能的转换,因此必须做出关于走哪条路径的决策。
动态规划旨在找到一个最优解,其中
来源: thinkings/dynamic-programming.md4-21
当问题具有以下特征时,动态规划尤其有用:
如果没有动态规划,解决具有这些特征的问题通常会导致由于重复计算而产生的指数级时间复杂度。
来源: thinkings/dynamic-programming.md174-191 thinkings/dynamic-programming.md135-146
每个动态规划方法都包含三个基本要素:
状态定义是动态规划中最关键的部分。它涉及到如何表示问题在不同阶段的状态以及需要跟踪哪些信息。正确的状态定义对于开发高效的解决方案至关重要。
例如,在许多字符串问题中,状态可能是 dp[i],表示字符串前 i 个字符的某个属性。对于涉及两个字符串的问题,状态可能是 dp[i][j],表示字符串 1 的前 i 个字符和字符串 2 的前 j 个字符的属性。
来源: thinkings/dynamic-programming.md239-252
状态转移方程是显示如何从一个状态转移到另一个状态的公式。它定义了不同状态之间的关系,对于计算每个状态的值至关重要。
例如,在斐波那契数列问题中,转移方程将是
dp[i] = dp[i-1] + dp[i-2]
来源: thinkings/dynamic-programming.md419-432 problems/322.coin-change.md95-101
基本情况(或初始状态)定义了最简单子问题的起始值。这些是构建所有其他状态的基础。
对于斐波那契数列
dp[0] = 0
dp[1] = 1
来源: thinkings/dynamic-programming.md398-416
有两种两种主要的方法来实现动态规划解决方案:
自顶向下方法使用带有记忆化的递归。它从原始问题开始,将其分解为子问题,缓存结果以避免重复计算。
来源: thinkings/dynamic-programming.md154-166
自底向上方法从最简单的情况开始,构建子问题解的表格,然后逐步向上计算到原始问题。
来源: thinkings/dynamic-programming.md617-649
许多动态规划解决方案可以通过仅存储当前计算所需的状态来优化空间。
例如,在斐波那契数列中,我们只需要前两个值来计算下一个值。
来源: thinkings/dynamic-programming.md630-649 problems/198.house-robber.md82-93
动态规划可以应用于各种类型的问题:
涉及数组、字符串或序列的问题,其中解决方案取决于按特定顺序处理元素。
例如:最大子数组和 problems/53.maximum-sum-subarray-cn.md
来源: problems/53.maximum-sum-subarray-cn.md110-129
涉及从具有不同价值和约束条件的项目集合中进行选择的问题。
例如:硬币找零 problems/322.coin-change.md
来源: problems/322.coin-change.md210-221
涉及在网格或矩阵上进行遍历或计算的问题。
涉及区间的问题,通常需要不同的状态定义方法。
来源: thinkings/dynamic-programming.md685-702
状态空间很大且需要被压缩的问题。
来源: thinkings/dynamic-programming.md703-706
涉及数字的位数及其属性的问题。
来源: thinkings/dynamic-programming.md707-711
让我们以硬币找零问题为例,作为动态规划的完整示例。
问题:给定不同面额的硬币和一个总金额,找出凑成该金额所需的最少硬币数。
dp[i] = 凑成金额 i 所需的最少硬币数coindp[i] = min(dp[i], dp[i-coin] + 1) 如果 i-coin >= 0dp[0] = 0 (凑成金额 0 需要 0 枚硬币)amount+1 (表示“不可能”)如果 dp[amount] 小于 amount+1,则答案是 dp[amount],否则为 -1
来源: problems/322.coin-change.md94-101 problems/322.coin-change.md209-221
| 方面 | 记忆化(自顶向下) | 制表(自底向上) |
|---|---|---|
| 方法 | 带缓存的递归 | 填充表格的迭代 |
| 计算方式 | 按需(惰性) | 全面(积极) |
| 实现 | 通常更容易实现 | 有时实现起来更复杂 |
| 堆栈使用 | 使用递归堆栈 | 无递归堆栈 |
| 空间优化 | 难以优化空间 | 通常允许空间优化 |
| 子问题 | 仅解决必要的子问题 | 解决指定范围内的所有子问题 |
来源: thinkings/dynamic-programming.md658-675
当以下情况时使用动态规划:
来源: thinkings/dynamic-programming.md202-216
来源: thinkings/dynamic-programming.md730-731
以下是一些推荐的练习题,以帮助您掌握动态规划: