目录
1.题目描述:
给定两个单词 word1
和 word2
,计算出将 word1
转换成word2
所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
-
插入一个字符
-
删除一个字符
-
替换一个字符
-
题目中隐含的操作:跳过某个字符
2.示例:
输入: word1 = "intention", word2 = "execution"
输出: 5
解释:
intention -> inention (删除 't')
inention -> enention (将 'i' 替换为 'e')
enention -> exention (将 'n' 替换为 'x')
exention -> exection (将 'n' 替换为 'c')
exection -> execution (插入 'u')
3.问题分析
编辑距离问题就是给我们两个字符串s1和s2
,只能用三种操作,让我们把s1
变成s2
,求最少的操作数。需要明确的是,不管是把s1
变成s2
还是反过来,结果都是一样的,所以后文就以s1
变成s2
举例。解决两个字符串的动态规划问题,一般都是用两个指针i,j
分别指向两个字符串的最后,然后一步步往前走,缩小问题的规模。
4.绘图分析
5.代码设计
- 伪代码:
if(s1[i]==s2[j]) return dp(i-1,j-1);
else{
三种操作三选一:
(1)删除: return dp(i-1,j)+1;
(2)插入: return dp(i,j-1)+1;
(3)替换: return dp(i-1,j-1)+1;
}
- 函数实现:用一个dp数组记录需要变化的步数. dp[i][j]表示字符串长度为i变为字符串长度为j需要的步数。一般来说,处理两个字符串的动态规划问题,都是建立 DP table。因为易于找出状态转移的关系:
#include <iostream>
using namespace std;
#include <string.h>
#include <math.h>
#include<vector>
void print(vector<vector<int>>& dp){
for(int i=0;i<dp.size();i++){
for(int j=0;j<dp[0].size();j++){
cout<<dp[i][j]<<" ";
}
cout<<endl;
}
}
int mindistance(string s1,string s2) //循环实现
{
int len1=s1.length();
int len2=s2.length();
int x=len1+1;
int y=len2+1;
vector<vector<int>> dp(x, vector<int> (y));
for(int i=0;i<x;i++){
dp[i][0]=i;
}
for(int j=0;j<y;j++){
dp[0][j]=j;
}
//自底向上求解
for(int i=1;i<=len1;i++){
for(int j=1;j<=len2;j++){
//注意字符串的开始条件判断的下标不是I和J二是I-1和J-1
if(s1[i-1]==s2[j-1]){
dp[i][j]=dp[i-1][j-1];
}
else{
dp[i][j]=min(dp[i-1][j],min(dp[i][j-1],dp[i-1][j-1]))+1;
}
}
}
print(dp);
return dp[len1][len2];
}
int main()
{
int num=mindistance("mart","karma");
cout<<num<<endl;
return 0;
}
6.结果展示
j=0 j=1 j=2 j=3 j=4 j=5
i=0 0 1 2 3 4 5
i=1 1 1 2 3 3 4
i=2 2 2 1 2 3 3
i=3 3 3 2 1 2 3
i=4 4 4 3 2 2 3
显然最优的操作次数是三:
(1)将m替换成k;
(2)将t替换成m;
(3)插入a;
7.题目扩展
既然知道了最少的操作数,是不是能将具体在哪一步进行的什么操作给出,肯定是有方法的,那就是稍微修改一下数据结构和程序,增加一些节点信息。
Node dp[len1][len2];
struct Node {
int val;
int choice;
// 0 代表啥都不做
// 1 代表插入
// 2 代表删除
// 3 代表替换
}
val
属性就是之前的 dp 数组的数值(最小编辑距离),choice
属性代表操作。在做最优选择时,顺便把操作记录下来,然后就从结果反推具体操作。
#include <iostream>
using namespace std;
#include <string.h>
#include <math.h>
#include<vector>
struct node{
int val;
int type;
//0----skip
//1----insert
//2----delete
//3----replace
};
void print(vector<vector<int>>& dp){
for(int i=0;i<dp.size();i++){
for(int j=0;j<dp[0].size();j++){
cout<<dp[i][j]<<" ";
}
cout<<endl;
}
cout<<endl;
}
void printhandle(vector<vector<node>>& handle){
for(int i=0;i<handle.size();i++){
for(int j=0;j<handle[0].size();j++){
cout<<handle[i][j].val<<"-->"<<handle[i][j].type<<" ";
}
cout<<endl;
}
cout<<endl;
}
int mindistance(string s1,string s2) //循环实现
{
int len1=s1.length();
int len2=s2.length();
int x=len1+1;
int y=len2+1;
vector<vector<int>> dp(x, vector<int> (y));
for(int i=0;i<x;i++){
dp[i][0]=i;
}
for(int j=0;j<y;j++){
dp[0][j]=j;
}
vector<vector<node>> handle(x, vector<node> (y));
for(int i=0;i<x;i++){
handle[i][0].type=i;
}
for(int j=0;j<y;j++){
handle[0][j].type=j;
}
//自底向上求解
for(int i=1;i<=len1;i++){
for(int j=1;j<=len2;j++){
//注意字符串的开始条件判断的下标不是I和J二是I-1和J-1
if(s1[i-1]==s2[j-1]){
dp[i][j]=dp[i-1][j-1];
handle[i][j].type=0; //0----skip
handle[i][j].val=dp[i][j];
}
else{
dp[i][j]=min(dp[i-1][j],min(dp[i][j-1],dp[i-1][j-1]))+1;
if(dp[i][j]==dp[i-1][j]+1){
handle[i][j].type=2; //2----delete
}
else if(dp[i][j]==dp[i][j-1]+1){
handle[i][j].type=1; //1----insert
}
else{
handle[i][j].type=3; //3----replace
}
handle[i][j].val=dp[i][j];
}
}
}
print(dp);
printhandle(handle);
return dp[len1][len2];
}
int main()
{
int num=mindistance("ebc","adfcg");
cout<<num<<endl;
return 0;
}
输出结果:
0 1 2 3 4 5
1 1 2 3 4 5
2 2 2 3 4 5
3 3 3 3 3 4
val-->type
0-->0 0-->1 0-->2 0-->3 0-->4 0-->5
0-->1 1-->3 2-->1 3-->1 4-->1 5-->1
0-->2 2-->2 2-->3 3-->1 4-->1 5-->1
0-->3 3-->2 3-->2 3-->3 3-->0 4-->1
4
请按任意键继续. . .
回推路径:
0-->0 0-->1 0-->2 0-->3 0-->4 0-->5
0-->1 1-->3 2-->1 3-->1 4-->1 5-->1
0-->2 2-->2 2-->3 3-->1 4-->1 5-->1
0-->3 3-->2 3-->2 3-->3 3-->0 4-->1