🚀 动态规划四步法则深度解析(以编辑距离为例)
本文将以力扣72题编辑距离为例,完整演示动态规划四步法则的应用过程。
一、🔍 审题分析(问题建模)
🔧 操作限制:允许三种原子操作
- 插入一个字符(耗时1单位)
- 删除一个字符(耗时1单位)
- 替换一个字符(耗时1单位)
🎯 所求目标 :将字符串A转换为字符串B的最小操作次数
⚠️ 特殊边界:
- 当A为空时,操作次数等于B的长度(全插入)
- 当B为空时,操作次数等于A的长度(全删除)
二、📐 方案设计(三要素建模)
2.1 状态定义(阶段与状态变量)
java
// dp[i][j]表示将A的前i个字符转换为B的前j个字符的最小代价
int[][] dp = new int[m+1][n+1]; // m=A.length(), n=B.length()
阶段划分:
- 每个字符处理视为一个阶段,总阶段数为max(m,n)
- 状态变量
(i,j)
表示处理到A的第i个字符和B的第j个字符的状态
2.2 转移方程推导(决策过程)
java
if (A.charAt(i-1) == B.charAt(j-1)) {
dp[i][j] = dp[i-1][j-1]; // 字符相同无需操作
} else {
// 三种决策取最小值
dp[i][j] = Math.min(
dp[i-1][j] + 1, // 删除A[i](进入i-1阶段)
Math.min(
dp[i][j-1] + 1, // 插入B[j](进入j-1阶段)
dp[i-1][j-1] + 1 // 替换字符(同时进入i-1,j-1阶段)
)
);
}
决策依据:
- 删除操作:消除A的当前字符,状态回退到
i-1
- 插入操作:添加B需要的字符,状态推进到
j-1
- 替换操作:修改字符后双方同时推进
2.3 边界条件设置
java
// 初始化第一行(A为空,全插入操作)
for (int j=0; j<=n; j++) dp[0][j] = j;
// 初始化第一列(B为空,全删除操作)
for (int i=0; i<=m; i++) dp[i][0] = i;
三、🔬 执行验证(代码实现)
3.1 基础版实现
java
public int minDistance(String word1, String word2) {
int m = word1.length(), n = word2.length();
int[][] dp = new int[m+1][n+1];
// 边界初始化
for (int i=0; i<=m; i++) dp[i][0] = i;
for (int j=0; j<=n; j++) dp[0][j] = j;
// 状态转移
for (int i=1; i<=m; i++) {
for (int j=1; j<=n; j++) {
if (word1.charAt(i-1) == word2.charAt(j-1)) {
dp[i][j] = dp[i-1][j-1];
} else {
dp[i][j] = Math.min(Math.min(
dp[i-1][j],
dp[i][j-1]),
dp[i-1][j-1]
) + 1;
}
}
}
return dp[m][n];
}
3.2 测试案例验证
java
// 测试案例1:leetcode -> 3次操作(替换l→e,替换e→t,删除d)
assert minDistance("horse", "ros") == 3;
// 测试案例2:空字符串处理
assert minDistance("", "algorithm") == 9;
四、⚡ 总结优化(复杂度分析)
4.1 复杂度对比表
方案 | 时间复杂度 | 空间复杂度 | 适用场景 |
---|---|---|---|
基础二维DP | O(mn) | O(mn) | 常规规模数据 |
滚动数组 | O(mn) | O(n) | 单行依赖场景 |
递归+备忘录 | O(mn) | O(mn) | 树状结构处理 |
4.2 滚动数组优化
java
public int minDistanceOpt(String A, String B) {
int m = A.length(), n = B.length();
int[] prev = new int[n+1];
// 初始化第一行
for (int j=0; j<=n; j++) prev[j] = j;
for (int i=1; i<=m; i++) {
int[] curr = new int[n+1];
curr[0] = i; // 每行第一个元素初始化
for (int j=1; j<=n; j++) {
if (A.charAt(i-1) == B.charAt(j-1)) {
curr[j] = prev[j-1];
} else {
curr[j] = Math.min(prev[j], Math.min(curr[j-1], prev[j-1])) + 1;
}
}
prev = curr; // 滚动更新
}
return prev[n];
}
优化原理: 由于状态转移仅依赖上一行和当前行前一个元素,通过两个一维数组交替使用,将空间复杂度从O(mn)降为O(n)
🌟 知识延伸(不同路径问题)
问题描述
62.不同路径:机器人从m×n网格左上角到右下角的路径总数(只能向右/向下移动)
四步法则应用
-
审题分析
- 约束:单方向移动限制
- 目标:路径总数统计
- 边界:第一行/列只有1种路径
-
方案设计
java// dp[i][j] = dp[i-1][j] + dp[i][j-1] int[][] dp = new int[m][n]; // 状态压缩版 int[] dp = new int[n]; Arrays.fill(dp, 1); for (int i=1; i<m; i++) { for (int j=1; j<n; j++) { dp[j] += dp[j-1]; // 滚动累计 } }
-
复杂度对比
- 基础版:空间O(mn) → 压缩版:空间O(n)
- 时间复杂度均为O(mn)
📌 关键总结 :动态规划本质是通过状态定义将问题分解为多阶段决策过程,通过保存子问题解避免重复计算。掌握四步法则后,可系统化解决90%的DP问题!🚀