CHAPTER 09 · 状态推导

动态规划

从递归和重复子问题出发,定义状态、选择与状态转移方程。

4 个知识小节11 道分层练习7–10 天 建议周期
展开本章目录
  1. 学习之前
  2. 1. 从暴力递归到 DP
  3. 2. 线性状态与相邻选择
  4. 3. 背包与资源分配
  5. 4. 序列与二维 DP
  6. 延伸阅读
  7. 分层练习
  8. 我的笔记
00

BEFORE LEARNING

先理解为什么学

动态规划解决具有重复子问题和最优子结构的问题。学习重点是状态定义与转移来源,而不是背诵题型答案。

开始前应具备

  • 理解递归和回溯
  • 能分析子问题是否重复
  • 熟悉二维数组

完成本章后

  • 写清状态定义
  • 从递归改写备忘录
  • 掌握一维与二维基础模型
01

CORE LESSONS

知识讲义

01

LESSON

从暴力递归到 DP

什么时候想到它?

递归树中相同参数反复出现,而且大问题答案能由较小参数的答案推导。

先写出穷举所有选择的递归,再用备忘录缓存重复子问题,最后按依赖顺序改为自底向上。DP 表只是保存子问题答案的容器。

推导步骤

  1. 定义递归函数参数和返回值
  2. 列出当前所有选择
  3. 识别重复参数并加入备忘录
  4. 根据依赖方向确定表格遍历顺序
思维模板 / 伪代码
def dp(state):
  if state in memo: return memo[state]
  if base_case: return base
  memo[state] = best(dp(next_state) for choice in choices)
  return memo[state]
02

LESSON

线性状态与相邻选择

什么时候想到它?

答案沿数组位置推进,当前选择只依赖前一个或前几个位置。

爬楼梯、最大子数组和、打家劫舍都属于线性状态,但含义不同。先明确 dp[i] 是“到达 i 的方案数”“以 i 结尾的最优值”还是“前 i 个元素的最优值”。

推导步骤

  1. 定义 dp[i] 的完整语义
  2. 写出不选择和选择当前元素的结果
  3. 确定初始一到两个状态
  4. 只依赖少量旧状态时压缩空间
思维模板 / 伪代码
for i in range(1, n):
  current = choose_best(previous_states, a[i])
  shift(previous_states, current)
03

LESSON

背包与资源分配

什么时候想到它?

从若干物品中选择,使容量、金额或目标和满足条件;物品可能只能一次或可重复使用。

容量是状态维度,物品是决策层。0/1 背包的一维优化要倒序遍历容量,避免同一物品在本轮重复使用;完全背包则通常正序。

推导步骤

  1. 确定物品是否可重复
  2. 定义容量状态表示可行性、方案数或最优值
  3. 写二维转移确认依赖
  4. 压缩为一维时选择正确遍历方向
思维模板 / 伪代码
for item in items:
  for capacity in reversed(range(item, target + 1)):
    dp[capacity] = combine(dp[capacity], dp[capacity - item])
04

LESSON

序列与二维 DP

什么时候想到它?

状态同时依赖两个序列位置,或一个区间的左右边界。

二维状态常表示两个前缀之间的最优关系。先处理字符相等时的转移,再处理不等时删除、跳过或替换等选择。

推导步骤

  1. 定义 dp[i][j] 对应哪两个前缀或区间
  2. 写出空前缀的初始化
  3. 区分相等与不等分支
  4. 按依赖方向填表并解释最终单元格
思维模板 / 伪代码
for i in range(1, m + 1):
  for j in range(1, n + 1):
    if a[i-1] == b[j-1]: match_transition()
    else: choose_from_neighbors()
02

READING

延伸阅读

先读完本站讲义并尝试练习;遇到推导不清的地方,再回到原始教程深入阅读。

03

PRACTICE LADDER

按作用分层练习

题量由知识点决定,不再固定为 5 道。先完成“入门”和“模板”,再做变式与综合;挑战题可以留到复习阶段。

入门认识结构模板独立复现变式迁移条件综合组合技巧挑战延后突破
01

从暴力递归到 DP

1 道 · 依次完成

LC 509入门简单

斐波那契数

Fibonacci Number

为什么选:从指数递归直观看到重复子问题。

备忘录建议 15 分钟
打开 LeetCode ↗
02

线性状态与相邻选择

4 道 · 依次完成

LC 70入门简单

爬楼梯

Climbing Stairs

为什么选:建立一维状态、初始值和空间压缩。

线性 DP建议 20 分钟
打开 LeetCode ↗
LC 53模板中等

最大子数组和

Maximum Subarray

为什么选:区分“以当前位置结尾”的状态定义。

状态压缩建议 25 分钟
打开 LeetCode ↗
LC 198模板中等

打家劫舍

House Robber

为什么选:当前房屋选择与否形成标准相邻决策。

选择 DP建议 30 分钟
打开 LeetCode ↗
LC 213变式中等

打家劫舍 II

House Robber II

为什么选:把环形约束拆为两个线性子问题。

环形 DP建议 35 分钟
打开 LeetCode ↗
03

背包与资源分配

2 道 · 依次完成

LC 322模板中等

零钱兑换

Coin Change

为什么选:完全背包中允许重复使用硬币并求最少数量。

完全背包建议 40 分钟
打开 LeetCode ↗
LC 416综合中等

分割等和子集

Partition Equal Subset Sum

为什么选:把等和分割转成容量为总和一半的 0/1 背包。

0/1 背包建议 45 分钟
打开 LeetCode ↗
04

序列与二维 DP

4 道 · 依次完成

LC 300变式中等

最长递增子序列

Longest Increasing Subsequence

为什么选:明确以 i 结尾的最长递增子序列。

序列 DP建议 40 分钟
打开 LeetCode ↗
LC 1143模板中等

最长公共子序列

Longest Common Subsequence

为什么选:两个前缀相等或不等时的二维状态转移。

二维 DP建议 45 分钟
打开 LeetCode ↗
LC 72挑战中等

编辑距离

Edit Distance

为什么选:综合插入、删除、替换三种选择。

二维 DP建议 60 分钟
打开 LeetCode ↗
LC 5挑战中等

最长回文子串

Longest Palindromic Substring

为什么选:练习区间状态和按长度推进的填表顺序。

区间 DP建议 55 分钟
打开 LeetCode ↗
04

PERSONAL NOTEBOOK

我的学习笔记

我如何识别这个模式?今天卡在哪里?下次需要先检查什么?

笔记保存在当前浏览器