动态规划(Dynamic Programming, DP) 是算法中解决最优化问题的核心方法之一。其核心思想是将原问题分解为多个重叠子问题,并通过记忆化存储避免重复计算。
本文从基础概念到经典题型进行系统分类与解析,涵盖从入门到进阶的所有常见问题。
状态定义:明确 dp[i]
或 dp[i][j]
的含义
示例:
dp[i]
表示以nums[i]
结尾的最长递增子序列长度
状态转移方程:找到子问题之间的关系
示例:
dp[i] = max(dp[j] + 1)
,其中j < i
且nums[j] < nums[i]
初始化:设定边界条件
示例:
dp[0] = 1
遍历顺序:根据依赖关系确定顺序
示例:一维DP常从左到右,二维DP需双重循环
dp[i]
表示爬到第 i
阶的方法数dp[i] = dp[i-1] + dp[i-2]
dp[0] = 1
, dp[1] = 1
dp[i]
表示偷到第 i
家的最大金额dp[i] = max(dp[i-1], dp[i-2] + nums[i])
dp[i]
表示以 nums[i]
结尾的 LIS 长度dp[i] = max(dp[j] + 1)
,其中 j < i
且 nums[j] < nums[i]
dp[i]
表示以 nums[i]
结尾的最大子数组和dp[i] = max(nums[i], dp[i-1] + nums[i])
dp[i][j]
表示前 i
个物品能否凑出重量 j
dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i]]
(target + sum(nums)) / 2
的背包问题dp[i]
表示凑出金额 i
的最小硬币数dp[i] = min(dp[i], dp[i-coin] + 1)
dp[i][j]
表示子串 s[i..j]
是否为回文dp[i][j] = (s[i] == s[j]) && (j-i <= 2 || dp[i+1][j-1])
dp[i][j]
表示戳破区间 (i,j)
内气球的最大得分dp[i][j] = max(dp[i][k] + dp[k][j] + nums[i]*nums[k]*nums[j])
dp[node][0/1]
表示偷/不偷当前节点的最大金额dp[node][1] = val + dp[left][0] + dp[right][0]
dp[node][0] = max(dp[left][0], dp[left][1]) + max(dp[right][0], dp[right][1])
dp[i][0]
:第 i
天不持有股票的最大利润dp[i][1]
:第 i
天持有股票的最大利润dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i])
类型 | 经典题目 |
---|---|
线性DP | 70. 爬楼梯、198. 打家劫舍 |
序列问题 | 300. LIS、1143. LCS |
背包问题 | 416. 分割等和子集、322. 零钱兑换 |
区间DP | 5. 最长回文子串、312. 戳气球 |
树形DP | 337. 打家劫舍 III |
状态机DP | 121. 买卖股票 I、309. 含冷冻期 |
动态规划的本质是“用空间换时间”,通过拆解问题、记录中间结果来避免重复计算。
掌握路径建议:
📌 推荐练习顺序:线性DP → 序列问题 → 背包问题 → 区间DP → 树形DP → 状态机DP
本文作者:Dong
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC。本作品采用《知识共享署名-非商业性使用 4.0 国际许可协议》进行许可。您可以在非商业用途下自由转载和修改,但必须注明出处并提供原作者链接。 许可协议。转载请注明出处!