P2758 编辑距离 题解复盘
类型:字符串DP(编辑距离)
目标:将字符串 A 转换为字符串 B,使操作次数最少。
基本信息
| 项目 | 内容 |
|---|---|
| 题目编号、来源 | P2758 洛谷 |
| 训练层级 | B普及 |
| 知识版块 | 字符串DP、编辑距离、二维DP |
解题前・关键信号识别
| 维度 | 分析 |
|---|---|
| 目标、约束、底层结构 | 将字符串 A 转换成字符串 B,允许删除、插入、修改三种操作,求最少操作次数。 |
| 数据规模 | 字符串长度 ≤2000,需要 O(n²) 算法。 |
| 候选算法和依据 | 每个状态都与两个字符串的前缀有关,使用二维DP。 |
| 复杂度预判 | 时间复杂度:O(nm) 空间复杂度:O(nm) |
解题后・外化复盘
| 维度 | 内容 |
|---|---|
| 状态定义 | dp[i][j] 表示:将字符串 A 的前 i 个字符转换成字符串 B 的前 j 个字符所需要的最少操作次数。 |
| 状态转移 | 若 a[i-1]==b[j-1]: dp[i][j]=dp[i-1][j-1] 否则: dp[i][j]=min(dp[i-1][j]+1, dp[i][j-1]+1, dp[i-1][j-1]+1) |
| 遍历顺序 | 按 i、j 正序遍历,因为每个状态依赖左、上、左上三个状态。 |
| 实现结构 / 核心思路 | ① 初始化第一行、第一列。 ② 双重循环枚举两个字符串前缀。 ③ 相同字符直接继承。 ④ 不同字符枚举删除、插入、修改三种操作。 |
| 错因回溯 | ① 一开始倒序遍历,状态依赖方向错误。 ② 使用 a[i]、b[j],导致字符串越界,正确应使用 a[i-1]、b[j-1]。 ③ 初始化后仍从 (0,0) 开始更新,导致访问 dp[-1][*]。 |
| 边界和易错点 | 1. dp[i][0]=i(删除 i 次)。 2. dp[0][j]=j(插入 j 次)。 3. DP 从 (1,1) 开始。 4. 字符比较使用 a[i-1]、b[j-1]。 |
| 下次看到什么信号,我应该想到这个方法 | 看到: ① 字符串转换 ② 删除、插入、修改 ③ 求最少操作次数 想到:编辑距离(二维DP)。 |
三种操作理解
假设:
text
A → B
始终是在修改第一个字符串 A。
删除
例如:
text
abc
↓
ac
删除:
text
b
对应:
cpp
dp[i-1][j]+1
插入
例如:
text
ab
↓
abc
插入:
text
c
对应:
cpp
dp[i][j-1]+1
修改
例如:
text
cat
↓
cut
修改:
text
a
↓
u
对应:
cpp
dp[i-1][j-1]+1
为什么这样初始化?
第一列
例如:
text
abc
↓
空串
只能:
text
删除3次
所以:
cpp
dp[3][0]=3;
即:
cpp
dp[i][0]=i;
第一行
例如:
text
空串
↓
abc
只能:
text
插入3次
所以:
cpp
dp[0][3]=3;
即:
cpp
dp[0][j]=j;
为什么字符相同不用操作?
例如:
text
abc
abc
最后一个字符:
text
c
c
已经相同。
因此:
不用修改。
直接:
cpp
dp[i][j]=dp[i-1][j-1];
AC完整代码
cpp
#include<iostream>
#include<algorithm>
using namespace std;
int dp[2005][2005];
int main()
{
string a,b;
cin>>a>>b;
int len1=a.size();
int len2=b.size();
for(int i=0;i<=len1;i++)
dp[i][0]=i;
for(int j=0;j<=len2;j++)
dp[0][j]=j;
for(int i=1;i<=len1;i++)
{
for(int j=1;j<=len2;j++)
{
if(a[i-1]==b[j-1])
{
dp[i][j]=dp[i-1][j-1];
}
else
{
dp[i][j]=min(
dp[i-1][j]+1,
min(
dp[i][j-1]+1,
dp[i-1][j-1]+1
)
);
}
}
}
cout<<dp[len1][len2];
return 0;
}
模型总结
| 模型 | 状态 | 转移 |
|---|---|---|
| LCS | dp[i][j] |
max() |
| 编辑距离 | dp[i][j] |
min() |
| 路径DP | dp[i][j] |
从相邻位置转移 |
| 区间DP | dp[l][r] |
从左右区间转移 |
LCS 与 编辑距离 对比
| 内容 | LCS | 编辑距离 |
|---|---|---|
| 求什么 | 最长公共子序列长度 | 最少操作次数 |
| 字符相同 | +1 |
不加 |
| 字符不同 | max() |
min() |
| 初始化 | 全部为0 | 第一行、第一列分别为0~n |
| 状态 | 前缀最长公共子序列 | 前缀最少编辑次数 |
记忆
text
看到:
① 字符串转换
② 删除
③ 插入
④ 修改
⑤ 求最少操作次数
↓
想到:
编辑距离
状态:
dp[i][j]
↓
字符相同:
dp[i-1][j-1]
↓
字符不同:
删除:
dp[i-1][j]+1
插入:
dp[i][j-1]+1
修改:
dp[i-1][j-1]+1
↓
取最小值