目录
[二、题 1:不同路径(LeetCode 62)](#二、题 1:不同路径(LeetCode 62))
[1. 题目描述](#1. 题目描述)
[2. 核心理论](#2. 核心理论)
[3. 基础解法:二维 DP](#3. 基础解法:二维 DP)
[4. 优化解法 1:一维 DP(空间 O (n))](#4. 优化解法 1:一维 DP(空间 O (n)))
[5. 优化解法 2:数学公式(O (1) 空间)](#5. 优化解法 2:数学公式(O (1) 空间))
[三、题 2:最小路径和(LeetCode 64)](#三、题 2:最小路径和(LeetCode 64))
[1. 题目描述](#1. 题目描述)
[2. 核心理论](#2. 核心理论)
[3. 基础解法:二维 DP](#3. 基础解法:二维 DP)
[4. 优化解法:原地修改(空间 O (1))](#4. 优化解法:原地修改(空间 O (1)))
[四、题 3:最长回文子串(LeetCode 5)](#四、题 3:最长回文子串(LeetCode 5))
[1. 题目描述](#1. 题目描述)
[2. 核心理论](#2. 核心理论)
[3. 基础解法:二维 DP](#3. 基础解法:二维 DP)
[4. 优化解法:中心扩展法(空间 O (1))](#4. 优化解法:中心扩展法(空间 O (1)))
[五、题 4:最长公共子序列(LeetCode 1143)](#五、题 4:最长公共子序列(LeetCode 1143))
[1. 题目描述](#1. 题目描述)
[2. 核心理论](#2. 核心理论)
[3. 基础解法:二维 DP](#3. 基础解法:二维 DP)
[4. 优化解法:一维 DP(空间 O (n))](#4. 优化解法:一维 DP(空间 O (n)))
[六、题 5:编辑距离(LeetCode 72)](#六、题 5:编辑距离(LeetCode 72))
[1. 题目描述](#1. 题目描述)
[2. 核心理论](#2. 核心理论)
[3. 基础解法:二维 DP](#3. 基础解法:二维 DP)
[4. 优化解法:一维 DP(空间 O (n))](#4. 优化解法:一维 DP(空间 O (n)))
[1. 多维 DP 的核心共性](#1. 多维 DP 的核心共性)
[2. 解题技巧](#2. 解题技巧)
本次总结覆盖 5 道多维 DP 经典中等题,涵盖「路径问题、子串 / 子序列问题」,每道题提供基础解法 + 优化解法,拆解核心逻辑与难点:
一、题型总览
| 题目 | 核心场景 | 基础解法(时间 / 空间) | 优化解法(时间 / 空间) | 核心难点 |
|---|---|---|---|---|
| 不同路径 | 网格路径计数 | 二维 DP(O (mn)/O (mn)) | 一维 DP / 数学公式(O (mn)/O (n)) | 边界初始化 + 路径方向约束 |
| 最小路径和 | 网格路径最优值 | 二维 DP(O (mn)/O (mn)) | 原地修改(O (mn)/O (1)) | 左 / 上方向的最小和递推 |
| 最长回文子串 | 连续子串匹配 | 二维 DP(O (n²)/O (n²)) | 中心扩展法(O (n²)/O (1)) | 回文的嵌套 + 对称特性 |
| 最长公共子序列 | 非连续子序列 | 二维 DP(O (mn)/O (mn)) | 一维 DP(O (mn)/O (n)) | 子序列的非连续匹配 |
| 编辑距离 | 字符串转换代价 | 二维 DP(O (mn)/O (mn)) | 一维 DP(O (mn)/O (n)) | 三种编辑操作的状态映射 |
二、题 1:不同路径(LeetCode 62)
1. 题目描述
m×n 网格,从左上角到右下角,每次只能向右 / 向下 移动,求不同路径数。示例:m=3, n=7 → 输出 28。
2. 核心理论
状态定义:dp[p][q] = 到达(p,q)的不同路径数。状态转移:dp[p][q] = dp[p-1][q] + dp[p][q-1](来自上方 / 左方的路径数之和)。
3. 基础解法:二维 DP
java
public int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];
// 第一行:只能从左到右,路径数恒为1
for (int q = 0; q < n; q++) dp[0][q] = 1;
// 第一列:只能从上到下,路径数恒为1
for (int p = 0; p < m; p++) dp[p][0] = 1;
// 非边界
for (int p = 1; p < m; p++) {
for (int q = 1; q < n; q++) {
dp[p][q] = dp[p-1][q] + dp[p][q-1];
}
}
return dp[m-1][n-1];
}
4. 优化解法 1:一维 DP(空间 O (n))
观察到dp[p][q]仅依赖上一行的 q 列 和当前行的 q-1 列,用一维数组滚动更新:
java
public int uniquePaths(int m, int n) {
int[] dp = new int[n];
Arrays.fill(dp, 1); // 第一行初始为1
for (int p = 1; p < m; p++) {
for (int q = 1; q < n; q++) {
dp[q] += dp[q-1]; // 等价于 dp[p][q] = dp[p-1][q] + dp[p][q-1]
}
}
return dp[n-1];
}
5. 优化解法 2:数学公式(O (1) 空间)
本质是组合问题 :从(m-1)+(n-1)步中选m-1步向下(或n-1步向右),公式为:路径数=Cm+n−2m−1=(m−1)!(n−1)!(m+n−2)!
java
public int uniquePaths(int m, int n) {
long res = 1;
// 计算组合数 C(m+n-2, min(m-1,n-1))
int k = Math.min(m-1, n-1);
for (int i = 1; i <= k; i++) {
res = res * (m + n - 2 - i + 1) / i;
}
return (int) res;
}
三、题 2:最小路径和(LeetCode 64)
1. 题目描述
m×n 网格,求从左上角到右下角的最小路径和 (每次只能向右 / 向下)。示例:grid=[[1,3,1],[1,5,1],[4,2,1]] → 输出 7。
2. 核心理论
状态定义:dp[p][q] = 到达(p,q)的最小路径和。状态转移:dp[p][q] = min(dp[p-1][q], dp[p][q-1]) + grid[p][q]。
3. 基础解法:二维 DP
java
public int minPathSum(int[][] grid) {
int m = grid.length, n = grid[0].length;
int[][] dp = new int[m][n];
dp[0][0] = grid[0][0];
// 第一行
for (int q = 1; q < n; q++) dp[0][q] = dp[0][q-1] + grid[0][q];
// 第一列
for (int p = 1; p < m; p++) dp[p][0] = dp[p-1][0] + grid[p][0];
// 非边界
for (int p = 1; p < m; p++) {
for (int q = 1; q < n; q++) {
dp[p][q] = Math.min(dp[p-1][q], dp[p][q-1]) + grid[p][q];
}
}
return dp[m-1][n-1];
}
4. 优化解法:原地修改(空间 O (1))
直接在原数组上更新,无需额外空间:
java
public int minPathSum(int[][] grid) {
int m = grid.length, n = grid[0].length;
// 第一行
for (int q = 1; q < n; q++) grid[0][q] += grid[0][q-1];
// 第一列
for (int p = 1; p < m; p++) grid[p][0] += grid[p-1][0];
// 非边界
for (int p = 1; p < m; p++) {
for (int q = 1; q < n; q++) {
grid[p][q] += Math.min(grid[p-1][q], grid[p][q-1]);
}
}
return grid[m-1][n-1];
}
四、题 3:最长回文子串(LeetCode 5)
1. 题目描述
求字符串中最长的连续回文子串 。示例:s="babad" → 输出"bab"或"aba"。
2. 核心理论
状态定义:dp[left][right] = 子串s[left..right]是否为回文。状态转移:
- 若
s[left] == s[right],则dp[left][right] = dp[left+1][right-1](长度 > 2)或true(长度≤2)。
3. 基础解法:二维 DP
java
public String longestPalindrome(String s) {
int n = s.length();
boolean[][] dp = new boolean[n][n];
int start = 0, maxLen = 1;
// 长度为1的子串
for (int i = 0; i < n; i++) dp[i][i] = true;
// 长度为2的子串
for (int i = 0; i < n-1; i++) {
if (s.charAt(i) == s.charAt(i+1)) {
dp[i][i+1] = true;
start = i;
maxLen = 2;
}
}
// 长度≥3的子串
for (int len = 3; len <= n; len++) {
for (int left = 0; left <= n - len; left++) {
int right = left + len - 1;
if (s.charAt(left) == s.charAt(right) && dp[left+1][right-1]) {
dp[left][right] = true;
start = left;
maxLen = len;
}
}
}
return s.substring(start, start + maxLen);
}
4. 优化解法:中心扩展法(空间 O (1))
所有回文有一个 "中心",从中心向两边扩展:
java
public String longestPalindrome(String s) {
int n = s.length();
int start = 0, maxLen = 1;
for (int i = 0; i < n; i++) {
// 奇数长度中心(单个字符)
int len1 = expand(s, i, i);
// 偶数长度中心(两个字符)
int len2 = expand(s, i, i+1);
int len = Math.max(len1, len2);
if (len > maxLen) {
start = i - (len - 1) / 2;
maxLen = len;
}
}
return s.substring(start, start + maxLen);
}
private int expand(String s, int left, int right) {
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
left--;
right++;
}
return right - left - 1;
}
五、题 4:最长公共子序列(LeetCode 1143)
1. 题目描述
求两个字符串的最长公共子序列 长度(子序列非连续)。示例:text1="abcde", text2="ace" → 输出 3。
2. 核心理论
状态定义:dp[p][q] = text1前p个字符与text2前q个字符的最长公共子序列长度。状态转移:
- 若
text1[p-1] == text2[q-1],则dp[p][q] = dp[p-1][q-1] + 1; - 否则
dp[p][q] = max(dp[p-1][q], dp[p][q-1])。
3. 基础解法:二维 DP
java
public int longestCommonSubsequence(String text1, String text2) {
int m = text1.length(), n = text2.length();
int[][] dp = new int[m+1][n+1];
for (int p = 1; p <= m; p++) {
for (int q = 1; q <= n; q++) {
if (text1.charAt(p-1) == text2.charAt(q-1)) {
dp[p][q] = dp[p-1][q-1] + 1;
} else {
dp[p][q] = Math.max(dp[p-1][q], dp[p][q-1]);
}
}
}
return dp[m][n];
}
4. 优化解法:一维 DP(空间 O (n))
观察到dp[p][q]仅依赖上一行的 q 列 和当前行的 q-1 列,用一维数组:
java
public int longestCommonSubsequence(String text1, String text2) {
int m = text1.length(), n = text2.length();
int[] dp = new int[n+1];
for (int p = 1; p <= m; p++) {
int prev = dp[0]; // 保存dp[p-1][q-1]
for (int q = 1; q <= n; q++) {
int temp = dp[q]; // 临时保存当前dp[q](即dp[p-1][q])
if (text1.charAt(p-1) == text2.charAt(q-1)) {
dp[q] = prev + 1;
} else {
dp[q] = Math.max(dp[q], dp[q-1]);
}
prev = temp;
}
}
return dp[n];
}
六、题 5:编辑距离(LeetCode 72)
1. 题目描述
求将word1转换为word2的最少操作数 (操作:插入、删除、替换)。示例:word1="horse", word2="ros" → 输出 3。
2. 核心理论
状态定义:dp[p][q] = 将word1前p个字符转为word2前q个字符的最少操作数。状态转移:
- 若
word1[p-1] == word2[q-1],则dp[p][q] = dp[p-1][q-1]; - 否则
dp[p][q] = min(dp[p-1][q], dp[p][q-1], dp[p-1][q-1]) + 1(删除、插入、替换)。
3. 基础解法:二维 DP
java
public int minDistance(String word1, String word2) {
int m = word1.length(), n = word2.length();
int[][] dp = new int[m+1][n+1];
// 初始化:空串转成word2前q个字符(插入q次)
for (int q = 0; q <= n; q++) dp[0][q] = q;
// 初始化:word1前p个字符转成空串(删除p次)
for (int p = 0; p <= m; p++) dp[p][0] = p;
// 递推
for (int p = 1; p <= m; p++) {
for (int q = 1; q <= n; q++) {
if (word1.charAt(p-1) == word2.charAt(q-1)) {
dp[p][q] = dp[p-1][q-1];
} else {
dp[p][q] = Math.min(Math.min(dp[p-1][q], dp[p][q-1]), dp[p-1][q-1]) + 1;
}
}
}
return dp[m][n];
}
4. 优化解法:一维 DP(空间 O (n))
用一维数组滚动更新,保存上一行状态:
java
public int minDistance(String word1, String word2) {
int m = word1.length(), n = word2.length();
int[] dp = new int[n+1];
// 初始化
for (int q = 0; q <= n; q++) dp[q] = q;
for (int p = 1; p <= m; p++) {
int prev = dp[0]; // 保存dp[p-1][0]
dp[0] = p; // 当前行的dp[0](删除p次)
for (int q = 1; q <= n; q++) {
int temp = dp[q]; // 保存dp[p-1][q]
if (word1.charAt(p-1) == word2.charAt(q-1)) {
dp[q] = prev;
} else {
dp[q] = Math.min(Math.min(dp[q], dp[q-1]), prev) + 1;
}
prev = temp;
}
}
return dp[n];
}
七、整体总结
1. 多维 DP 的核心共性
- 状态定义:多以 "前 p 个 / 前 q 个""到达 (p,q) 位置" 为核心,拆分问题规模;
- 状态转移:依赖 "更小的子问题"(如左 / 上位置、前 p-1/q-1 个字符);
- 优化方向:通过 "滚动数组" 或 "原地修改" 将空间复杂度从 O (mn) 降到 O (n) 或 O (1)。
2. 解题技巧
- 路径问题:关注移动方向,边界初始化(第一行 / 列);
- 子串 / 子序列问题:区分 "连续(子串)" 与 "非连续(子序列)",子串需绑定 "左右边界",子序列需绑定 "前 p/q 个";
- 字符串问题:编辑距离、最长公共子序列等,状态定义多为 "前 p 个字符转前 q 个字符"。
