返回文章列表
数据结构与算法
算法动态规划学习路线

动态规划专题导读

很多同学一看到"动态规划"四个字就头皮发麻,其实 DP 没有想象中那么玄学。它本质上就是一种用空间换时间的递推思想:把大问题拆成小问题,把小问题的答案存起来,避免重复计算。这篇导读会帮你建立动规的整体认知,后续文章再逐个模块击破。

一、什么时候该用 DP?

不是所有题目都要上 DP,遇到下面三个信号,基本可以确定往动规方向想:

  1. 求最值:问"最多/最少/最长/最大"能是多少,例如最少硬币数、最长子序列。
  2. 求方案数:问"有多少种方法/多少条路径",例如爬楼梯的不同走法。
  3. 后面状态依赖前面状态:当前决策会受之前选择的影响,且过程中有重叠子问题(同一个小问题会被反复计算)。

反过来的信号也要记住:如果题目只要求输出具体方案本身(比如把每条路径都列出来),DP 存的只是数量,通常得老老实实回溯;如果每一步局部最优能直接推出全局最优且无需反悔,那是贪心的地盘。

再提两个术语:

  • 最优子结构:大问题的最优解可以由小问题的最优解推导出来。这是 DP 能工作的前提。
  • 重叠子问题:递归展开后同一个子问题出现多次。这是 DP 比暴力递归快的原因。

二、解题五部曲

做动规题最忌讳上来就写代码。建议固定走五步,每一步都问自己一句话:

步骤 要回答的问题 以 70. 爬楼梯为例
1. 状态定义 dp[i] 的下标和值分别代表什么? dp[i]:爬到第 i 阶的方法数
2. 递推公式 dp[i] 从哪些更小的状态推来? 第 i 阶只能从 i-1 跨 1 步或 i-2 跨 2 步上来:dp[i] = dp[i-1] + dp[i-2]
3. 初始化 哪些状态推不出来,必须直接给值? dp[0] = 1,dp[1] = 1(递推的起点)
4. 遍历顺序 保证算 dp[i] 时依赖项已就绪 从小到大:for i in 2..n
5. 打印调试 答案不对时,把 dp 数组打出来对照 逐项检查 dp 是从哪一项开始偏离预期

五部曲中最容易被轻视的是第 3 步和第 4 步。初始化错一位、遍历方向反了,代码看起来"完全正确"却输出诡异结果——这时候第 5 步打印 dp 数组是最快的排错手段,比盯着代码干瞪眼高效得多。

用 Java 把爬楼梯写完整:

class Solution {
    public int climbStairs(int n) {
        if (n <= 2) return n;
        // 1. dp[i] 表示爬到第 i 阶的方法数
        int[] dp = new int[n + 1];
        // 3. 初始化递推起点
        dp[1] = 1;
        dp[2] = 2;
        // 4. 从小到大,保证 dp[i-1]、dp[i-2] 已算好
        for (int i = 3; i <= n; i++) {
            // 2. 递推公式
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
}

三、DP、记忆化递归、贪心怎么选?

这三种思想经常被混为一谈,用一张表说清它们的分工:

维度 记忆化递归(自顶向下) 动态规划(自底向上) 贪心
思路 大问题拆小问题,递归 + 缓存 从最小子问题递推到大问题 每步取局部最优
实现 递归 + memo 数组 迭代 + dp 数组 通常排序或扫描
空间 递归栈 + 缓存 只需 dp 表(可滚动压缩) 通常 O(1)
风险 栈溢出、重复状态难察觉 遍历顺序易写错 局部最优不保证全局最优
适用 状态转移是树形/图状、状态稀疏 状态密集、依赖关系规整 能证明贪心策略成立

三者可以互相转化:把记忆化递归的递归栈展开就是 DP;如果贪心策略恰好能被证明正确(如跳跃游戏),它比 DP 更快。拿不准时优先写 DP,它是三者中最通用、最不容易出反例的。

四、本专题地图

接下来几篇会按下面的顺序推进,每个模块的侧重点不同:

  1. 背包问题:01 背包与完全背包的遍历顺序之辨(倒序 vs 正序),以及"组合数 vs 排列数"的双层循环嵌套顺序——背包是半个动规的地基。
  2. 打家劫舍与股票:状态机 DP 的两个招牌。打家劫舍从线性走到环形再到树形;股票系列把"持有/不持有"两状态扩展出交易次数、冷冻期等维度。
  3. 子序列与编辑距离:单串(LIS)、双串(LCS)、编辑距离的二维 dp 表画法,以及区间 DP 入门(回文子串)。

五、LeetCode 题单总表

按模块分块列出,建议做完一块再进下一块:

入门热身(体会五部曲)

题号 题名 一句话考点
509 斐波那契数 最原始的递推,五部曲走一遍流程
70 爬楼梯 递推公式就是变形斐波那契
746 使用最小花费爬楼梯 从"方案数"过渡到"求最值"
62 不同路径 二维 dp 的初始化与遍历
63 不同路径 II 二维 dp 遇到障碍物如何跳过
343 整数拆分 递推要在多个拆分方案中取最大
96 不同的二叉搜索树 枚举左右子树规模的乘积求和

背包问题

题号 题名 一句话考点
416 分割等和子集 01 背包的"能否恰好装满"判定
1049 最后一块石头的重量 II 把对半分的思路转成 01 背包
494 目标和 加减号转化为子集和的计数
474 一和零 二维费用的 01 背包
518 零钱兑换 II 完全背包求组合数,外物内背
377 组合总和 IV 完全背包求排列数,外背内物
322 零钱兑换 完全背包求最少硬币数
279 完全平方数 物品是平方数的完全背包
139 单词拆分 完全背包思路判断可达性

打家劫舍与股票

题号 题名 一句话考点
198 打家劫舍 相邻不能偷的线性状态机
213 打家劫舍 II 环形拆成两条链取最大
337 打家劫舍 III 树形 DP,偷/不偷两个状态后序遍历
121 买卖股票的最佳时机 只能交易一次的最简状态机
122 买卖股票的最佳时机 II 可多次交易的两状态递推
123 买卖股票的最佳时机 III 限定 2 次交易的状态维度扩展
188 买卖股票的最佳时机 IV 把 2 次推广到 k 次交易
309 最佳买卖股票时机含冷冻期 卖出后多一个冷冻状态
714 买卖股票的最佳时机含手续费 交易时扣手续费的变形

子序列与编辑距离

题号 题名 一句话考点
300 最长递增子序列 O(n²) DP 与贪心+二分双解法
674 最长连续递增序列 连续版只需和前一个比
718 最长重复子数组 双串 DP 求公共连续子数组
1143 最长公共子序列 双串 DP 的经典模板
1035 不相交的线 题面换皮,本质就是 LCS
53 最大子序和 一维 dp + 负数直接重开的贪心味
392 判断子序列 LCS 思想的简化版
115 不同的子序列 删串计数,二维 DP 难题
583 两个字符串的删除操作 编辑距离的简化版
72 编辑距离 增删改三操作的状态转移
647 回文子串 区间 DP 统计回文个数
516 最长回文子序列 区间 DP 求最长回文

六、学习建议

  • 每题先自己写五部曲的前两步(状态定义 + 递推公式),卡住了再看题解,看完必须合上题解重写一遍。
  • 背包部分务必亲手画一维滚动数组的更新表格,倒序遍历不亲手推一遍永远记不牢。
  • 股票系列建议把 121 到 309 的状态转移表放在一张纸上对比,状态机的"加维度"套路一眼就通了。
  • 二维 DP(LCS、编辑距离、回文)要练到能徒手画出 dp 表并填出前几行。

下一篇我们从背包问题开始,聊聊为什么容量必须倒序遍历这个经典细节。

参考