最小编辑代价
题目描述
给定两个字符串str1和str2,再给定三个整数ic,dc和rc,分别代表插入、删除和替换一个字符的代价,请输出将str1编辑成str2的最小代价。
输入描述:
输出三行,第一行和第二行均为一行字符串,分别表示两个字符串str1,str2。 ( 1 ≤ l e n g t h ( s t r 1 ) , l e n g t h ( s t r 2 ) ≤ 5000 ) \left( 1\leq length(str1),length(str2) \leq 5000 \right) (1≤length(str1),length(str2)≤5000)。第三行为三个正整数,代表ic,dc和rc。(1<=ic<=10000、1<=dc<=10000、1<=rc<=10000)
输出描述:
输出一个整数,表示编辑的最小代价。
示例1
输入
abc
adc
5 3 2
输出
2
示例2
输入
abc
adc
5 3 100
输出
8
示例3
输入
abc
abc
5 3 2
输出
0
备注:
时间复杂度 O ( n ∗ m ) O(n*m) O(n∗m) ,空间复杂度 O ( n ) O(n) O(n) 。(n,m代表两个字符串长度)
题解:
普通解法:
明显需要使用二维状态表,假设为:F[i, j],表示 str1[0…i] 转换成 str2[0…j] 的最小代价,则 F[i, j] 可以来自以下三个状态:
- F [ i − 1 , j − 1 ] F[i-1, j-1] F[i−1,j−1] :如果 s t r 1 [ i ] = s t r 2 [ j ] str1[i] = str2[j] str1[i]=str2[j],则 F [ i , j ] = F [ i − 1 , j − 1 ] F[i, j] = F[i - 1, j - 1] F[i,j]=F[i−1,j−1];否则的话,可以对 s t r 1 [ i ] str1[i] str1[i] 位置进行替换,则 F [ i , j ] = F [ i − 1 , j − 1 ] + r c F[i, j]= F[i-1, j-1] + rc F[i,j]=F[i−1,j−1]+rc;
- F [ i − 1 , j ] F[i-1, j] F[i−1,j]:表示删除 s t r 1 [ i ] str1[i] str1[i] 元素,则 F [ i , j ] = F [ i − 1 , j ] + d c F[i, j] = F[i-1, j] + dc F[i,j]=F[i−1,j]+dc;
- F [ i , j − 1 ] F[i, j-1] F[i,j−1]:表示插入 s t r 2 [ j ] str2[j] str2[j] 元素,则 F [ i , j ] = F [ i , j − 1 ] + i c F[i, j] = F[i, j-1] + ic F[i,j]=F[i,j−1]+ic。
上面几个状态取最小值即可。
普通解法代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 5010;
int f[N][N];
char str1[N];
char str2[N];
int ic, dc, rc;
int main(void) {
scanf("%s", str1);
scanf("%s", str2);
scanf("%d%d%d", &ic, &dc, &rc);
int len1 = strlen(str1);
int len2 = strlen(str2);
for (int i = 1; i <= len2; ++i) f[0][i] = i * ic;
for (int j = 1; j <= len1; ++j) f[j][0] = j * dc;
for (int i = 1; i <= len1; ++i) {
for (int j = 1; j <= len2; ++j) {
if (str1[i - 1] == str2[j - 1]) f[i][j] = f[i - 1][j - 1];
else f[i][j] = f[i - 1][j - 1] + rc;
f[i][j] = min(f[i][j], min(f[i - 1][j] + dc, f[i][j - 1] + ic));
}
}
printf("%d\n", f[len1][len2]);
return 0;
}
进阶解法:
题目要求额外空间为 O ( n ) O(n) O(n),所以,我们需要压缩上述二维状态表,一般方法是使用滚动数组。
我们看到,F[i, j] 跟 F[i-1, j-1]、F[i-1, j]、F[i, j-1] 有关,那么我们可以从左往右遍历。因为需要用到 F[i-1, j-1] 的值,所以我们需要额外保存上一轮的值。(此题是个不错的锻炼使用滚动数组的题目)
进阶解法代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 5010;
int f[N];
char str1[N];
char str2[N];
int ic, dc, rc;
int main(void) {
scanf("%s", str1);
scanf("%s", str2);
scanf("%d%d%d", &ic, &dc, &rc);
int len1 = strlen(str1);
int len2 = strlen(str2);
for (int i = 1; i <= len2; ++i) f[i] = i * ic;
for (int i = 1; i <= len1; ++i) {
int lu = f[0];
f[0] = i * dc;
for (int j = 1; j <= len2; ++j) {
int ans = f[j];
if (str1[i - 1] == str2[j - 1]) f[j] = lu;
else f[j] = lu + rc;
f[j] = min(f[j], min(ans + dc, f[j - 1] + ic));
lu = ans;
}
}
printf("%d\n", f[len2]);
return 0;
}