动态规划核心笔记(含10道必刷母题)
专治:不会设计DP数组、不知道开几维、不会写转移方程,可直接背、直接套,刷动态规划够用。
第一部分:DP核心方法论(必背)
一、看到题,先问自己3句话
- 我要算什么结果?
- 要算出这个结果,我需要记住哪几个变量?
- 这些变量,能不能用「前一步/小问题c推出来?
能 → 就是DP;不能 → 换思路
二、DP数组维度怎么定?(万能规则)
维度 = 你必须记录的「独立变量个数」
- 只跟「位置i」有关 → 一维 dp「i」
- 跟「位置i + 容量/状态j」有关 → 二维 dp「i」「j」
- 跟「i、j、k三个条件」有关 → 三维 dp「i」「j」「k」
最常见维度场景
- 一维DP:爬楼梯、斐波那契、打家劫舍、最大子数组和;dp「i」:前i个/到第i个的答案
- 二维DP:背包问题(物品i + 容量j)、路径问题(行i + 列j)、两个字符串(i位置 + j位置);dp「i」「j」:前i个物品,容量j时的最优值
- 三维DP(少见):股票问题(天数 + 持仓 + 交易次数)、两个字符串 + 额外状态
三、DP数组大小怎么定?
通用写法:
- 一维:dp「n+1」
- 二维:dp「n+1」「m+1」
四、DP定义模板(直接套)
- 模板1:序列/一维:dp「i」 = 前i个元素 / 走到第i个位置时的「最优值/方案数/真假」
- 模板2:背包/二维:dp「i」「j」 = 前i个物品,使用容量j时的最大价值
- 模板3:路径/网格:dp「i」「j」 = 走到(i,j)时的路径数/最小代价
- 模板4:双字符串:dp「i」「j」 = 第一个串前i个,第二个串前j个的最长公共子序列/编辑距离
五、怎么快速想出状态转移?
只看两种情况:
- 选 / 不选:不选 → dp「i」 = dp「i-1」;选 → dp「i」 = dp「i-...」 + ...
- 从哪来:上面 → dp「i-1」「j」;左边 → dp「i」「j-1」;左上 → dp「i-1」「j-1」
核心一句话: 当前状态 = 前面几种可能状态里选最优/相加。
六、DP做题万能步骤(背下来)
- 确定DP维度(1D / 2D / 3D)
- 写DP含义(这一步最关键)
- 确定DP大小
- 写初始化(dp「0」, dp「0」「0」...)
- 写转移方程
- 确定答案在哪(dp「n」, dp「n」「m」...)
七、你最卡的点:怎么知道开几行几列?(肉眼判断法)
- 题目给1个数组/1个字符串 → 一维 dp「n」
- 题目给2个字符串 / 网格m×n / 物品+背包 → 二维 dp「n+1」「m+1」
- 题目有"最多k次" / "有/无股票"这种附加状态 → 三维 dp「i」「j」「k」
八、循环能开几层?看数据范围!(关键技巧)
核心原则:比赛题目默认时间限制 1 秒,能承受约 10⁸ 次运算。
时间复杂度速查表(必背)
| 数据范围 | 可接受复杂度 | 能开几层循环 | 典型算法 |
|---|---|---|---|
| n ≤ 10 | O(n!) / O(2ⁿ) | 暴力枚举、全排列 | DFS、状压DP |
| n ≤ 20 | O(2ⁿ) | 指数级 | 状压DP、meet-in-middle |
| n ≤ 100 | O(n³) | 三层循环 OK | Floyd、区间DP |
| n ≤ 1000 | O(n²) | 双层循环 OK | 朴素DP、LIS O(n²) |
| n ≤ 10⁴ | O(n²) 勉强 | 双层循环卡边界 | 需优化常数 |
| n ≤ 10⁵ | O(n log n) | 单层 + log | 二分、排序、线段树 |
| n ≤ 2×10⁵ | O(n log n) | 单层 + log | LIS O(n log n) 版本 |
| n ≤ 10⁶ | O(n) | 只能单层 | 线性DP、前缀和 |
| n ≤ 10⁷ | O(n) | 单层要小心 | 常数要小 |
| n ≤ 10⁸ | O(log n) / O(√n) | 几乎不能循环 | 数学推导 |
实战判断技巧
技巧1:先看最大数据范围,倒推复杂度上限
题目:1 ≤ n ≤ 2×10⁵
分析:n² = 4×10¹⁰ >> 10⁸,双层循环必超时
结论:必须 O(n log n) 或 O(n)
技巧2:多个变量时,看乘积
题目:1 ≤ N, M ≤ 1000, 0 ≤ Q ≤ 3
分析:N × M = 10⁶ < 10⁸,二维DP可行
Q 很小,可以再套一层 → O(N × M × Q) ≈ 3×10⁶,稳过
结论:大胆开三维循环
技巧3:看是否有"小变量"可以枚举
题目:n ≤ 10⁵,k ≤ 20
分析:虽然 n 大,但 k 很小
结论:O(n × k) 或 O(n × 2ᵏ) 都可行
DP中的循环层数判断
一维DP转移:
dp「i」 = dp「i-1」 + ...→ O(n),n ≤ 10⁶ 都没问题dp「i」 = max(dp「j」 + ...) for all j < i→ O(n²),需要 n ≤ 1000
二维DP转移:
dp「i」「j」 = dp「i-1」「j」 + dp「i」「j-1」→ O(n×m),n×m ≤ 10⁷ 没问题dp「i」「j」 = max(dp「i-1」「k」 + ...) for all k→ O(n×m×k),要小心
常见坑点提醒
| 数据范围 | 你以为 | 实际情况 |
|---|---|---|
| n ≤ 2×10⁵ | 双层循环能过 | 必超时,要想办法优化 |
| n ≤ 1000 | 三层循环能过 | n³ = 10⁹,超时,只能双层 |
| n ≤ 500 | 三层循环 | n³ = 1.25×10⁸,卡边界,常数要小 |
| n ≤ 100 | 三层循环 | n³ = 10⁶,稳过 |
| 多组测试用例 T 个 | 只看 n | 要算 T × 复杂度,可能爆 |
快速记忆口诀
十万以上看 log,双层循环必超时
千级以下双层稳,百级三层随便开
多组测试要留意,T 乘复杂度别忘算
九、DP万能口诀(背熟直接用)
- 一维DP:要么「以i结尾」,要么「前i个」。
- 二维DP:要么「网格路径」,要么「两个序列」,要么「物品+背包」。
- 转移方程:永远只有两件事 ——「从哪来 / 选不选」。
第二部分:10道必刷DP母题
1. 斐波那契 / 爬楼梯(一维入门)
【题目】 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶?
示例:n = 3,输出 3(1+1+1, 1+2, 2+1)
- dp定义:dp「i」 = 爬到第i阶的方法数
- 维度:1维
- 转移:dp「i」 = dp「i-1」 + dp「i-2」
- 初始化:dp「0」=1, dp「1」=1
2. 打家劫舍(选或不选)
【题目】 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。给定一个代表每个房屋存放金额的非负整数数组,计算你不触动警报装置的情况下,一夜之内能够偷窃到的最高金额。
示例:nums = 「2,7,9,3,1」,输出 12(偷2、9、1)
- dp定义:dp「i」 = 前i家能偷的最大金额
- 维度:1维
- 转移:dp「i」 = max(dp「i-1」, dp「i-2」 + nums「i」)
- 理解:不偷i / 偷i
3. 最大子数组和(以i结尾)
【题目】 给你一个整数数组 nums,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组是数组中的一个连续部分。
示例:nums = 「-2,1,-3,4,-1,2,1,-5,4」,输出 6(子数组 「4,-1,2,1」)
- dp定义:dp「i」 = 以第i个数结尾的最大子数组和
- 维度:1维
- 转移:dp「i」 = max(nums「i」, dp「i-1」 + nums「i」)
- 理解:重新开始 / 接前面
4. 最长上升子序列 LIS
【题目】 给你一个整数数组 nums,找到其中最长严格递增子序列的长度。子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。
示例:nums = 「10,9,2,5,3,7,101,18」,输出 4(子序列 「2,3,7,101」)
- dp定义:dp「i」 = 以i结尾的最长上升子序列长度
- 维度:1维
- 转移:dp「i」 = max(dp「i」, dp「j」+1) (j < i && nums「j」<nums「i」)
- 答案:max(dp)
复杂度分析:
- O(n²) 朴素版:n ≤ 1000 可用
- O(n log n) 优化版:n ≤ 2×10⁵ 需用此版本
5. 不同路径(网格)
【题目】 一个机器人位于一个 m x n 网格的左上角。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角。问总共有多少条不同的路径?
示例:m = 3, n = 7,输出 28
- dp定义:dp「i」「j」 = 走到(i,j)的路径数
- 维度:2维
- 转移:dp「i」「j」 = dp「i-1」「j」 + dp「i」「j-1」
- 初始化:第一行、第一列全1
6. 最小路径和
【题目】 给定一个包含非负整数的 m x n 网格 grid,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。每次只能向下或者向右移动一步。
示例:grid = 「「1,3,1」,「1,5,1」,「4,2,1」」,输出 7(路径 1→3→1→1→1)
- dp定义:dp「i」「j」 = 走到(i,j)的最小代价
- 维度:2维
- 转移:dp「i」「j」 = min(dp「i-1」「j」, dp「i」「j-1」) + grid「i」「j」
7. 01背包(最经典二维)
【题目】 有 n 个物品和一个容量为 W 的背包,每个物品有重量 w「i」 和价值 v「i」。每个物品只能用一次,求解将哪些物品装入背包,可使这些物品的总重量不超过背包容量,且总价值最大。
示例:n=4, W=5, w=「2,1,3,2」, v=「3,2,4,2」,输出 7(选物品1和3)
- dp定义:dp「i」「j」 = 前i个物品,背包容量j的最大价值
- 维度:2维
- 转移:
- 不选:dp「i-1」「j」
- 选:dp「i-1」「j-w「i」」 + v「i」
- dp「i」「j」 = max(上面两种)
复杂度分析: O(n×W),若 n≤1000, W≤1000 则 n×W=10⁶ 可过
8. 完全背包(硬币凑法 / 零钱兑换)
【题目】 给你一个整数数组 coins,表示不同面额的硬币;以及一个整数 amount,表示总金额。计算并返回可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。每种硬币的数量是无限的。
示例:coins = 「1,2,5」, amount = 11,输出 3(5+5+1)
- dp定义:dp「j」 = 凑成金额j的最小硬币数
- 维度:1维滚动
- 转移:dp「j」 = min(dp「j」, dp「j - coin」 + 1)
9. 最长公共子序列 LCS(双字符串)
【题目】 给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。如果不存在公共子序列,返回 0。子序列是不改变剩余字符顺序的情况下删除某些(或不删除)字符形成的序列。
示例:text1 = "abcde", text2 = "ace",输出 3(公共子序列 "ace")
- dp定义:dp「i」「j」 = s1前i个、s2前j个的最长公共子序列长度
- 维度:2维
- 转移:
- s1「i」==s2「j」 → dp「i」「j」 = dp「i-1」「j-1」 + 1
- 不等 → max(dp「i-1」「j」, dp「i」「j-1」)
复杂度分析: O(n×m),两个字符串长度都 ≤1000 时稳过
10. 编辑距离(难但易考)
【题目】 给你两个单词 word1 和 word2,请返回将 word1 转换成 word2 所使用的最少操作数。你可以对一个单词进行三种操作:插入一个字符、删除一个字符、替换一个字符。
示例:word1 = "horse", word2 = "ros",输出 3(horse→rorse→rose→ros)
- dp定义:dp「i」「j」 = s1前i个 → s2前j个的最小操作数
- 维度:2维
- 转移:
- 相等:dp「i」「j」 = dp「i-1」「j-1」
- 不等:min(删, 增, 改) + 1
补充提示
刷题时,先看题目对应哪类母题,直接套用对应DP定义和转移思路,先写对框架,再优化细节(比如空间压缩),新手不用急于求快,每道题练会「维度判断+定义+转移」即可。
浙公网安备 33010602011771号