原题链接:1276:【例9.20】编辑距离
【题目描述】
设A和B是两个字符串。我们要用最少的字符操作次数,将字符串A转换为字符串B。这里所说的字符操作共有三种:
1、删除一个字符;
2、插入一个字符;
3、将一个字符改为另一个字符。
对任意的两个字符串A和B,计算出将字符串A变换为字符串B所用的最少字符操作次数。
【输入】
第一行为字符串A;第二行为字符串B;字符串A和B的长度均小于2000。
【输出】
只有一个正整数,为最少字符操作次数。
【输入样例】
sfdqxbw
gfdgw
【输出样例】
4
【解题思路】
著名的“编辑距离”问题,可以使用动态规划算法来解决。具体思路如下:
1. 定义状态:
首先定义动态规划数组 dp[i][j]
,其中 dp[i][j]
表示字符串 A
的前 i
个字符转换成字符串 B
的前 j
个字符所需的最少操作次数。
2. 初始化边界:
初始化 dp[0][j]
和 dp[i][0]
:
dp[0][j]
表示空字符串变为字符串B
的前j
个字符需要的最少操作数,显然是j
(即插入操作)。dp[i][0]
表示字符串A
的前i
个字符变为空字符串需要的最少操作数,也是i
(即删除操作)。
3. 状态转移方程:
根据题意,每次操作可以是插入、删除或替换。因此,状态转移方程为:
- 如果
A[i] == B[j]
,即字符相同,不需要操作,因此dp[i][j] = dp[i-1][j-1]
。 - 如果
A[i] != B[j]
,字符不同,需要进行一次操作,可以是:- 插入操作,此时
dp[i][j] = dp[i][j-1] + 1
; - 删除操作,此时
dp[i][j] = dp[i-1][j] + 1
; - 替换操作,此时
dp[i][j] = dp[i-1][j-1] + 1
; 对于每个状态,我们选择上述三种情况的最小值作为dp[i][j]
的值。
- 插入操作,此时
3.1插入操作:
当我们考虑 dp[i][j] = dp[i][j-1] + 1
,意味着要将字符串 A
的前 i
个字符变为字符串 B
的前 j
个字符,我们可以先考虑将字符串 A
的前 i
个字符变为字符串 B
的前 j-1
个字符的情况(即 dp[i][j-1]
),然后在 A
的末尾插入一个与 B[j]
相同的字符,这样 A
就增加了一个与 B[j]
匹配的字符,使得 A
和 B
的前 j
个字符相匹配。这就是为什么要对 dp[i][j-1]
的基础上加 1
。
3.2 删除操作:
同理,dp[i][j] = dp[i-1][j] + 1
反映的是从字符串 A
的前 i-1
个字符变为字符串 B
的前 j
个字符的情况。这里我们只需删除 A[i]
这一个字符,就能使问题变为已解决的子问题 dp[i-1][j]
。因为删除操作使得 A
减少了一个字符,所以需要在 dp[i-1][j]
的基础上加 1
。
3.3 替换操作:
最后,dp[i][j] = dp[i-1][j-1] + 1
表示的是将 A[i]
替换为 B[j]
,使得 A
的前 i
个字符和 B
的前 j
个字符匹配。我们假设 A
的前 i-1
个字符和 B
的前 j-1
个字符已经匹配(dp[i-1][j-1]
),现在通过替换 A[i]
为 B[j]
,可以使得 A
和 B
都增加了一个匹配的字符,因此在 dp[i-1][j-1]
的基础上增加 1
。
这三个操作覆盖了将一个字符串变为另一个字符串可能需要的所有基本操作。动态规划的思想就是通过解决小规模的子问题来逐步构建出整个问题的解,这些操作反映了从子问题到原问题转换过程中所需进行的操作。在每一步,选择最小操作数,确保了编辑距离的最优性。
4. 计算状态:
按照从小到大的顺序计算 dp[i][j]
。
5. 输出结果:
最后 dp[A的长度][B的长度]
即为将字符串 A
转换为字符串 B
的最少操作次数。
【代码实现】
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
string A, B;
cin >> A >> B;
int n = A.size(), m = B.size();
vector<vector<int>> dp(n+1, vector<int>(m+1, 0));
// 初始化边界
for(int i = 0; i <= n; ++i) dp[i][0] = i;
for(int j = 0; j <= m; ++j) dp[0][j] = j;
// 计算所有dp值
for(int i = 1; i <= n; ++i) {
for(int j = 1; j <= m; ++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, // 删除
dp[i][j-1] + 1, // 插入
dp[i-1][j-1] + 1 // 替换
});
}
}
}
// 输出最终结果
cout << dp[n][m] << endl;
return 0;
}