多维动态规划(题单顺序)
热题 100 - 多维动态规划分组(按题单顺序):62 / 64 / 5 / 1143 / 72
本页包含题单「多维动态规划」分组的全部题目,顺序与 list.json 保持一致。
多维 DP 统一“落地步骤”(点击展开)
- 状态:
dp[i][j]表示什么(一定要带上“含义 + 范围”)。 - 转移:
dp[i][j]从哪些更小状态来,为什么不会漏/重。 - 初始化:边界(第一行/第一列/空串/长度为 0)怎么设,避免越界。
- 遍历顺序:保证用到的状态已经算好(常见是从小到大)。
- 优化:能否滚动数组(把二维压成一维),以及滚动时更新方向是否会污染数据。
62. 不同路径
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
示例 1:
输入:m = 3, n = 7 输出:28
示例 2:
输入:m = 3, n = 2 输出:3 解释: 从左上角开始,总共有 3 条路径可以到达右下角。 1. 向右 -> 向下 -> 向下 2. 向下 -> 向下 -> 向右 3. 向下 -> 向右 -> 向下
示例 3:
输入:m = 7, n = 3 输出:28
示例 4:
输入:m = 3, n = 3 输出:6
提示:
1 <= m, n <= 100- 题目数据保证答案小于等于
2 * 109
最优解(二维 DP:dp[i][j]=dp[i-1][j]+dp[i][j-1],可滚动成一维)
最优解讲解(通俗版 + 推理过程)
- 题意:你站在左上角,每次只能往右或往下走一步,问到右下角一共有多少种不同的走法?
- 核心想法:走到某一个格子时,你只能从它的上面一格下来,或者从它的左边一格过来,没有别的路。所以「到达这个格子的走法数」=「到达上面那格的走法数」+「到达左边那格的走法数」。写成式子就是:
dp[i][j] = dp[i-1][j] + dp[i][j-1]。 - 边界:第一行只能一直往右走,第一列只能一直往下走,所以第一行、第一列每个格子都只有 1 种走法,初始化为 1。
- 空间优化:算每一行时只用得到「上一行」的结果,所以可以用一维数组「滚动」:
dp[j]表示当前行第 j 列有多少种走法,更新时用「上一行的 dp[j]」加上「当前行左边的 dp[j-1]」,即dp[j] = dp[j] + dp[j-1]。
类似题目(网格 DP:来自上/左)
思路相同:当前格子的值只依赖「上面」和「左边」,转移时用一维滚动即可,例如:
dp[j] = dp[j] + dp[j-1]
64. 最小路径和
给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
示例 1:
输入:grid = [[1,3,1],[1,5,1],[4,2,1]] 输出:7 解释:因为路径 1→3→1→1→1 的总和最小。
示例 2:
输入:grid = [[1,2,3],[4,5,6]] 输出:12
提示:
m == grid.lengthn == grid[i].length1 <= m, n <= 2000 <= grid[i][j] <= 200
最优解(二维 DP:dp[i][j]=min(dp[i-1][j],dp[i][j-1])+grid[i][j])
最优解讲解(通俗版 + 推理过程)
- 和 62 一样,只是把“路径条数”换成“路径代价最小”。
- 到
grid[i][j]的最小代价只能来自上或左的最小代价,再加上当前格子权重。 - 一维滚动时:
dp[j]表示从上方来的最小和dp[j-1]表示从左方来的最小和(因为已更新到当前行)- 所以
dp[j] = min(dp[j], dp[j-1]) + grid[i][j]。
类似题目(网格最短路 DP:来自上/左取 min)
dp[j] = min(dp[j], dp[j-1]) + cost[i][j]
5. 最长回文子串
给你一个字符串 s,找到 s 中最长的 回文 子串。
示例 1:
输入:s = "babad" 输出:"bab" 解释:"aba" 同样是符合题意的答案。
示例 2:
输入:s = "cbbd" 输出:"bb"
提示:
1 <= s.length <= 1000s仅由数字和英文字母组成
最优解(中心扩展:每个中心向两边扩,O(n^2) 时间 O(1) 空间)
最优解讲解(通俗版 + 推理过程)
- 回文的定义:左右对称。
- 关键推理:最长回文一定有一个“中心”:
- 奇数长度回文中心是一个字符(比如
aba的中心是 b) - 偶数长度回文中心在两个字符之间(比如
abba的中心在 bb 之间)
- 奇数长度回文中心是一个字符(比如
- 所以我们枚举每个可能中心,然后向两边扩展,直到不再相等。
- 为什么是 (O(n^2)):中心有 (O(n)) 个,每次扩展最坏 (O(n))。
- 这题也可以用二维 DP 表
pal[i][j],同样是 (O(n^2)),中心扩展更省空间、更直观。
类似题目(中心扩展模板)
for center in all:expand(center, center)expand(center, center+1)
1143. 最长公共子序列
给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
- 例如,
"ace"是"abcde"的子序列,但"aec"不是"abcde"的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
示例 1:
输入:text1 = "abcde", text2 = "ace" 输出:3 解释:最长公共子序列是 "ace" ,它的长度为 3 。
示例 2:
输入:text1 = "abc", text2 = "abc" 输出:3 解释:最长公共子序列是 "abc" ,它的长度为 3 。
示例 3:
输入:text1 = "abc", text2 = "def" 输出:0 解释:两个字符串没有公共子序列,返回 0 。
提示:
1 <= text1.length, text2.length <= 1000text1和text2仅由小写英文字符组成。
最优解(二维 DP:dp[i][j] 表示前 i/前 j 的 LCS 长度)
最优解讲解(通俗版 + 推理过程)
题目在问什么?
想象有两排字母,顺序固定。你从第一排里挑几个、从第二排里也挑几个,要挑出一模一样的一串(顺序一致)。问:这串最长能有多长?
例:"abcde" 和 "ace",能挑出 "ace",长度 3。
推理的起点:先解决“小一点”的问题
如果只让你看两串的前几格,你也会算“这两小段里最长公共串多长”。我们就把这个答案记在一张表里:dp[i][j] = text1 前 i 个 与 text2 前 j 个 的最长公共子序列长度。
表从左到右、从上到下填。填到 (i, j) 时,左边、上边、左上的格子都早就填好了,我们只要想:多出来的这两个字符(text1 的第 i 个 和 text2 的第 j 个)该怎么用?
情况一:这两个字符刚好相同
那就太好了:它们可以一起配对,算进公共串里。
“前面的部分”我们早就算过了,就是左上角那一格 dp[i-1][j-1]。所以当前格 = 左上 + 1:
- dp[i][j] = dp[i-1][j-1] + 1
(就像两个人最后一位对上号了,长度自然多 1。)
情况二:这两个字符不一样
公共子序列的意思是:从两串里挑出同一串,也就是两边的字符要能一一配对(相同的才能配)。现在最后这两个字符不同,配不成一对,所以没法把“这两个”同时都算进公共串里。
那我们只能选一种「放弃」方式:要么放弃 text1 的最后这个(相当于只考虑 text1 前 i-1 个 和 text2 前 j 个),答案在上面那格 dp[i-1][j];要么放弃 text2 的最后这个(只考虑 text1 前 i 个 和 text2 前 j-1 个),答案在左边那格 dp[i][j-1]。我们要的是最长公共串,所以取这两种里更大的:
- dp[i][j] = max(上面, 左边) = max(dp[i-1][j], dp[i][j-1])
小结
- 最后一位相同 → 左上 +1(配对成功)。
- 最后一位不同 → 上面、左边取 max(只能丢掉其中一串的最后一位,选更优的那边)。
填完表,右下角 dp[m][n] 就是答案。
类似题目(字符串二维 DP:末尾相同走对角,不同取上/左 max)
套路一样:看两串「末尾」是否相同,相同就对角 +1,不同就从上/左取最大:
if (a[i - 1] === b[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1else dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1])
72. 编辑距离
给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
- 插入一个字符
- 删除一个字符
- 替换一个字符
示例 1:
输入:word1 = "horse", word2 = "ros" 输出:3 解释: horse -> rorse (将 'h' 替换为 'r') rorse -> rose (删除 'r') rose -> ros (删除 'e')
示例 2:
输入:word1 = "intention", word2 = "execution" 输出:5 解释: intention -> inention (删除 't') inention -> enention (将 'i' 替换为 'e') enention -> exention (将 'n' 替换为 'x') exention -> exection (将 'n' 替换为 'c') exection -> execution (插入 'u')
提示:
0 <= word1.length, word2.length <= 500word1和word2由小写英文字母组成
最优解(二维 DP:插入/删除/替换 三选一)
最优解讲解(通俗版 + 推理过程)
这题是字符串 DP 里的另一座大山,但本质和 LCS 是亲兄弟。掌握它,你对「字符串怎么一点点变形」会理解得非常透。
题目在干嘛?
给你两个单词:word1 → 变成 → word2。每次只能做 3 种操作:
- 插入一个字符
- 删除一个字符
- 替换一个字符
问:最少要几步?
例如 horse → ros 的一种做法:
- horse → rorse(替换 h→r)
- rorse → rose(删除 r)
- rose → ros(删除 e)
答案 = 3。
核心思路(一句话)
把大问题拆成:前 i 个字符变成前 j 个字符要几步。
DP 定义
dp[i][j] = 把 word1 前 i 个字符 变成 word2 前 j 个字符 的最少操作数。
注意:是“前 i 个”,不是下标 i(所以 i、j 从 1 到 m、n,0 留给空串)。
初始化(边界)
| 情况 | 含义 | 值 |
|---|---|---|
| dp[0][j] | 空串 → word2 前 j 个 | 需要插入 j 次 → j |
| dp[i][0] | word1 前 i 个 → 空串 | 需要删除 i 次 → i |
状态转移(灵魂所在)
看最后一个字符:word1[i-1] 和 word2[j-1]。
情况 1:两个字符相同
那最后这个字符不用管,等于没变:dp[i][j] = dp[i-1][j-1](前面怎么变,现在就怎么变)。
情况 2:字符不同
必须做一次操作(插入 / 删除 / 替换),取步数最少的一种:
| 操作 | 含义 | 转移 |
|---|---|---|
| 替换 | 把 word1[i-1] 换成 word2[j-1] | dp[i-1][j-1] + 1 |
| 删除 | 删掉 word1[i-1],word1 少一个再去匹配 word2 | dp[i-1][j] + 1 |
| 插入 | 在 word1 末尾插入 word2[j-1],相当于 word2 少一个先匹配 | dp[i][j-1] + 1 |
为什么对应这个格子?
- dp[i-1][j] + 1(删除):当前是“word1 前 i 个”对“word2 前 j 个”。若删掉 word1 的最后一个,剩下的问题就是“word1 前 i-1 个 → word2 前 j 个”,答案已经算在 dp[i-1][j] 里,再加这一次删除就是 dp[i-1][j] + 1。
- dp[i][j-1] + 1(插入):若在 word1 末尾插入 word2[j-1],插入后末尾已经对齐;等价于先解决“word1 前 i 个 → word2 前 j-1 个”(dp[i][j-1]),再用 1 步插入补上 word2 的最后一个。
- dp[i-1][j-1] + 1(替换):若把 word1[i-1] 替换成 word2[j-1],两边末尾就一样了;等价于先解决“word1 前 i-1 个 → word2 前 j-1 个”(dp[i-1][j-1]),再加这 1 次替换。
取最小:dp[i][j] = 1 + min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1])
过程直观理解
可以理解为:从左上角走到右下角,每一步都在做「最省操作的选择」。代码见上方最优解。
复杂度:时间 O(m×n),空间 O(m×n)(可优化到 O(n))。
和 LCS 的关系
| LCS | 编辑距离 | |
|---|---|---|
| 末尾相同 | 配对,+1 | 不操作,继承左上 |
| 末尾不同 | 取 max(丢一边) | 取 min + 1(替换/删/插) |
| 目标 | 求最长“保留” | 求最少“修改” |
LCS 是「尽量不改」,编辑距离是「最少改几步」,本质都是在比较两个字符串的对齐方式。
最通俗总结
- 字符一样 → 继承左上角。
- 字符不一样 → 从「替换、删除、插入」里选最便宜的一步。
延伸:若只允许插入和删除(不允许替换),有结论:编辑距离 = len1 + len2 − 2×LCS,能把两道题直接打通。
类似题目(编辑类 DP:三操作取 min)
if same: dp[i][j]=dp[i-1][j-1]else dp[i][j]=1+min(del, ins, rep)