最短编辑距离
题意
给定两个字符串\(A\)和\(B\),现在要将\(A\)经过若干操作变为\(B\),可进行的操作有:
- 删除–将字符串A中的某个字符删除。
- 插入–在字符串A的某个位置插入某个字符。
- 替换–将字符串A中的某个字符替换为另一个字符。
现在请你求出,将\(A\)变为\(B\)至少需要进行多少次操作。
数据范围
\(1 \leq |A|, |B| \leq 1000\)
思路
受最长公共子序列的启发,我们设\(f(i, j)\)表示字符串\(A[1 \sim i]\)变为\(B[1 \sim j]\)需要进行多少次操作(下标从\(1\)开始)。
考虑\(A\)的第\(i\)位和\(B\)的第\(j\)位,如果\(A[i] = B[j]\),那么\(f(i, j) = f(i - 1, j - 1)\)
如果\(A[i] \neq B[j]\),考虑三种操作:
- 如果进行删除操作,就是删除\(A\)的第\(i\)位,那么\(f(i, j) = f(i - 1, j) + 1\)
- 如果进行插入操作,相当于对\(B\)进行删除操作,就是删除\(B\)的第\(j\)位,因此\(f(i, j) = f(i, j - 1) + 1\)
- 如果进行替换操作,就是将\(A\)的第\(i\)位替换为\(B\)的第\(j\)位,因此\(f(i, j) = f(i - 1, j - 1) + 1\)
将这三种情况取最小即可。
下面考虑边界,考虑边界一般从遍历过程中用到的,但是之前没有初始化的值。这里就是\(f(i, 0) = i, 1 \leq i \leq n\)以及\(f(0, i) = i, 1 \leq i \leq m\)
代码
递推
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, m;
char a[N], b[N];
int f[N][N];
int main()
{
scanf("%d%s%d%s", &n, a + 1, &m, b + 1);
for(int i = 1; i <= n; i ++) f[i][0] = i;
for(int i = 1; i <= m; i ++) f[0][i] = i;
for(int i = 1; i <= n; i ++) {
for(int j = 1; j <= m; j ++) {
if(a[i] == b[j]) f[i][j] = f[i - 1][j - 1];
else f[i][j] = min(f[i - 1][j], min(f[i][j - 1], f[i - 1][j - 1])) + 1;
}
}
printf("%d\n", f[n][m]);
return 0;
}
记忆化搜索
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, m;
char a[N], b[N];
int f[N][N];
int dfs(int i, int j)
{
if(i == 0) return j;
if(j == 0) return i;
if(f[i][j] > 0) return f[i][j];
if(a[i] == b[j]) f[i][j] = dfs(i - 1, j - 1);
else f[i][j] = min(dfs(i - 1, j), min(dfs(i, j - 1), dfs(i - 1, j - 1))) + 1;
return f[i][j];
}
int main()
{
scanf("%d%s%d%s", &n, a + 1, &m, b + 1);
printf("%d\n", dfs(n, m));
return 0;
}