面试知识库

动态规划模板:从状态定义到转移方程的完整套路#

动态规划最难的地方,从来不是写代码,而是想明白“状态”到底是什么。一旦状态定义正确,题目往往就顺下来了。

⚡ 速记版模板#

// 模板用途:将原问题拆成子问题,按状态转移自底向上求解
for (状态1初始化) { // 初始化第一类基础状态(如 dp[0]、第一行、第一列)
}
for (状态2初始化) { // 初始化第二类基础状态(按题意补齐边界)
}
for (状态遍历顺序) { // 按依赖关系遍历,保证转移来源已计算
    dp[i] = 状态转移; // 根据状态定义写转移方程
}
return dp[目标状态]; // 返回题目要求的最终状态值
java

🎯 什么时候想到动态规划?#

当题目有以下特征时,优先考虑动态规划:

  • 求最优解:最大值、最小值、方案数、可行性
  • 问题可以拆成重复子问题
  • 当前答案依赖之前阶段的结果

Hot 100 里的典型题目:

💡 动态规划五步法#

推荐每次都按这个顺序想:

  1. dp[i] 或 dp[i][j] 表示什么
  2. 状态转移方程是什么
  3. 初始值怎么设
  4. 遍历顺序怎么定
  5. 是否可以滚动优化

🚀 模板一:一维线性 DP#

适合:

  • 爬楼梯
  • 打家劫舍
  • 完全平方数
  • 零钱兑换

常见思路#

  • dp[i] 表示到位置 i 的最优解
  • 当前状态通常由前 1 个、前 2 个或若干更小状态转移而来

🚀 模板二:背包型 DP#

适合:

  • 零钱兑换
  • 分割等和子集
  • 完全平方数
class Solution {
    public boolean canPartition(int[] nums, int capacity) {
        boolean[] dp = new boolean[capacity + 1]; // dp[c]:容量 c 是否可由若干元素恰好凑出
        dp[0] = true; // 容量 0 永远可达(什么都不选)

        for (int num : nums) { // 依次处理每个物品(每个数只能用一次)
            for (int current = capacity; current >= num; current--) { // 倒序遍历容量:防止同一轮重复使用当前 num
                dp[current] = dp[current] || dp[current - num]; // 不选 num 或选 num 两种情况
            }
        }

        return dp[capacity]; // 目标容量是否可达
    }
}
java

关键点:

  • 0/1 背包通常倒序遍历容量
  • 完全背包通常正序遍历容量

🚀 模板三:二维网格 DP#

适合:

  • 不同路径
  • 最小路径和

🚀 模板四:区间 / 字符串 DP#

适合:

  • 最长回文子串
  • 编辑距离
  • 最长公共子序列
  • 最长有效括号

🧠 常见题型怎么想?#

1. 最值问题#

例如:最大和、最小路径和、最长长度。

通常写法:

dp[i] = Math.max(...) // 最值型 DP:当前状态取多个来源中的最大值
java

或

dp[i] = Math.min(...) // 最值型 DP:当前状态取多个来源中的最小值
java

2. 可行性问题#

例如:能否拆分、能否到达。

通常写法:

dp[i] = dp[j] && condition // 可行性 DP:前置状态可行且当前条件成立,则当前可行
java

3. 计数问题#

例如:共有多少种方案。

通常写法:

dp[i] += dp[j] // 计数型 DP:把所有可转移来源的方案数累加
java

⚠️ 易错点#

  1. 状态定义不清

    • 写转移前先用一句话定义 dp 的含义
  2. 初始化错误

    • 很多 DP 错不是错在转移,而是错在第一行、第一列、空串、空数组
  3. 遍历顺序错误

    • 区间 DP、背包 DP、网格 DP 的顺序都不同
  4. 把贪心误写成 DP,或把 DP 误写成贪心

    • 如果当前选择会影响未来,通常更偏向 DP

🎨 面试时怎么说#

建议按这个顺序表达:

  1. 状态定义
  2. 转移来源
  3. 初始化
  4. 遍历顺序
  5. 复杂度

例如:

我定义 dp[i] 表示前 i 个位置时的最优解,那么当前状态可以由前一个或前两个状态转移得到,因此状态方程是……

📌 一句话总结#

动态规划的关键不是背题,而是学会稳定地问自己五个问题:

  • 状态是什么?
  • 怎么转移?
  • 初始值是什么?
  • 顺序怎么遍历?
  • 能不能优化空间?