编辑距离(Edit Distance) 是一类经典的动态规划问题,通常用于衡量两个字符串之间的相似度。其核心思想是通过最少的操作次数将一个字符串转换成另一个字符串。允许的操作通常包括:
- 插入一个字符
- 删除一个字符
- 替换一个字符
解题思路:
1. 问题分析:
- 给定两个字符串 ( A ) 和 ( B ),要求将 ( A ) 转换成 ( B ) 所需的最少操作次数。
- 操作包括插入、删除和替换。
2. 动态规划定义:
- 设 ( dp[i][j] ) 表示将 ( A ) 的前 ( i ) 个字符转换成 ( B ) 的前 ( j ) 个字符所需的最少操作次数。
3. 状态转移方程:
- 如果 ( A[i-1] == B[j-1] ):
- 当前字符相等,无需操作,直接继承上一个状态:
[
dp[i][j] = dp[i-1][j-1]
]
- 当前字符相等,无需操作,直接继承上一个状态:
- 如果 ( A[i-1] \neq B[j-1] ):
- 需要进行一次操作(插入、删除或替换),取以下三种情况的最小值并加 1:
[
dp[i][j] = \min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1
]- ( dp[i-1][j] ):删除 ( A[i-1] )。
- ( dp[i][j-1] ):插入 ( B[j-1] )。
- ( dp[i-1][j-1] ):替换 ( A[i-1] ) 为 ( B[j-1] )。
- 需要进行一次操作(插入、删除或替换),取以下三种情况的最小值并加 1:
4. 初始化:
- ( dp[0][j] = j ):将空字符串转换为 ( B ) 的前 ( j ) 个字符,需要 ( j ) 次插入操作。
- ( dp[i][0] = i ):将 ( A ) 的前 ( i ) 个字符转换为空字符串,需要 ( i ) 次删除操作。
5. 最终答案:
- ( dp[m][n] ),其中 ( m ) 是 ( A ) 的长度,( n ) 是 ( B ) 的长度。
代码模板:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int minDistance(string A, string B) {
int m = A.length();
int n = B.length();
// 定义 dp 数组
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
// 初始化
for (int i = 0; i <= m; i++) {
dp[i][0] = i; // 将 A 的前 i 个字符转换为空字符串,需要 i 次删除操作
}
for (int j = 0; j <= n; j++) {
dp[0][j] = j; // 将空字符串转换为 B 的前 j 个字符,需要 j 次插入操作
}
// 动态规划求解
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; 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], dp[i][j - 1], dp[i - 1][j - 1]}) + 1; // 取最小值并加 1
}
}
}
// 返回结果
return dp[m][n];
}
int main() {
string A, B;
cin >> A >> B;
int result = minDistance(A, B);
cout << result << endl;
return 0;
}
优化空间复杂度:
可以使用滚动数组将空间复杂度优化为 ( O(\min(m, n)) )。以下是优化后的代码:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int minDistance(string A, string B) {
int m = A.length();
int n = B.length();
// 使用滚动数组优化空间复杂度
vector<int> dp(n + 1, 0);
// 初始化
for (int j = 0; j <= n; j++) {
dp[j] = j; // 将空字符串转换为 B 的前 j 个字符,需要 j 次插入操作
}
// 动态规划求解
for (int i = 1; i <= m; i++) {
int prev = dp[0]; // 保存 dp[i-1][j-1]
dp[0] = i; // 将 A 的前 i 个字符转换为空字符串,需要 i 次删除操作
for (int j = 1; j <= n; j++) {
int temp = dp[j]; // 保存 dp[i-1][j]
if (A[i - 1] == B[j - 1]) {
dp[j] = prev; // 字符相等,无需操作
} else {
dp[j] = min({dp[j], dp[j - 1], prev}) + 1; // 取最小值并加 1
}
prev = temp; // 更新 prev
}
}
// 返回结果
return dp[n];
}
int main() {
string A, B;
cin >> A >> B;
int result = minDistance(A, B);
cout << result << endl;
return 0;
}
解题思路总结:
- 定义状态:明确 ( dp[i][j] ) 的含义。
- 状态转移:根据字符是否相等,分情况讨论。
- 初始化:处理边界条件(空字符串的情况)。
- 优化空间:使用滚动数组减少空间复杂度。
- 输出结果:返回 ( dp[m][n] )。
示例:
输入:
horse
ros
输出:
3
解释:
- 将 “horse” 转换为 “ros” 的最少操作次数是 3:
- 替换 ‘h’ 为 ‘r’:
rorse
- 删除 ‘r’:
rose
- 删除 ‘e’:
ros
- 替换 ‘h’ 为 ‘r’: