编辑距离

A 题面

设A和B是2个字符串。要用最少的字符操作将字符串A转换为字符串B。这里所说的字符操作包括:

(1)删除一个字符;
(2)插入一个字符;
(3)将一个字符改为另一个字符。

求将字符串A变换为字符串B所用的最少字符操作数称为字符串A到B的编辑距离,记为d(A,B)。

B 分析

我将做dp题目大致划分为四步:

  • 确定子问题
  • 根据子问题定义状态
  • 得到状态转移方程
  • 思考边界问题

1.确定子问题:

对于一个字符,我们有三种操作:增 删 改

(其实不操作也是一种操作)

所以本题的子问题就是:

在进行了这三种操作过后

现在的a串到b串用了多少步

2.根据子问题定义状态

因为子问题是求让 a 成为 b 的步数

所以我们定义f[i][j]表示 将串a[1…i]转换为串b[1…j]所需的最少操作次数

所以我们的答案就在f[lenA][lenB]

(注意a 和 A 不是同一个字符串,b 和 B 也不是)

(A是原串,B是原目标串)

(a是操作过后的原串,b是操作过后的B)

3.得到状态转移方程

(1)增:可以看做与B串最后一个字符抵消后不再考虑这个字符

所以f(i,j)=min(f(i,j),f(i,j-1)+1);

(2)删:可以看做把A串最后一个字符删去后不再考虑这个字符

所以f(i,j)=min(f(i,j),f(i-1,j)+1);

(3)改:可以看作抵消了A、B串最后的两个字符

所以f(i,j)=min(f(i,j),f(i-1,j-1)+1);

当然这种情况有特例,就是当A,B串这两个字符相等的时候

此时:f(i,j)=f(i-1,j-1);

for(int i=1;i<=lena;i++){
    for(int j=1;j<=lenb;j++){
        if(a[i-1]==b[j-1]){
            f[i][j]=f[i-1][j-1];
            continue;
        }
        f[i][j]=min(min(f[i-1][j],f[i][j-1]),f[i-1][j-1])+1;
    }
}

需要注意的是:在执行增删改操作后不要忘了+1

4.思考边界问题

(1)i==0时,即a串为空,那么对应的f[0][j]的值就为j:

这时的处理方法是:增加j个字符,使a转化为b

(2)j==0时,即b为空,那么对应的f[i][0]的值就为i:

这时的处理方法是:减少i个字符,使a转化为b

for(int i=0;i<=lena;i++)f[i][0]=i;
for(int i=0;i<=lenb;i++)f[0][i]=i;

5.答案

显然答案的位置就在f[lena][lenb];

C 代码

#include <bits/stdc++.h>
using namespace std;
int f[2005][2005];
char a[2005],b[2005];
int main(){
    int alen,blen;
    cin>>a>>b;
    alen=strlen(a);
    blen=strlen(b);
    for(int i=0;i<=alen;i++){
        f[i][0]=i;
    }
    for(int i=0;i<=blen;i++){
        f[0][i]=i;
    }
    for(int i=1;i<=alen;i++){
        for(int j=1;j<=blen;j++){
            if(a[i-1]==b[j-1]){
                f[i][j]=f[i-1][j-1];
            }
            else{
                f[i][j]=min(min(f[i-1][j],f[i][j-1]),f[i-1][j-1])+1;
            }
        }
    }
    cout<<f[alen][blen];
    return 0;
}
 
View Code

D 总结

在这里小小地总结一下如何解决一般dp问题:

1.确定子问题

2.根据子问题定义状态

3.得到状态转移方程

4.思考边界问题

5.写code

E 扩充

上面是解释记忆化搜索和dp关系的一张图

所以这道题其实可以用递归的方法写出来

#include<bits/stdc++.h> 
using namespace std;

char a[2005],b[2005];
int f[2005][2005];

int dp(int i,int j){ 
    if(f[i][j]!=-1)return f[i][j];//记忆化搜索 
    if(i==0) return f[i][j]=j;//边界 
    if(j==0) return f[i][j]=i;//边界 
    int bonus=1;              //是否执行了操作  
    if(a[i]==b[j])bonus=0;  //没有执行操作 
    return f[i][j]=min(min(dp(i-1,j)+1,dp(i,j-1)+1),dp(i-1,j-1)+bonus);
}

int main(){
    cin>>a>>b;
    memset(f,-1,sizeof(f));
    int lena=strlen(a);
    int lenb=strlen(b);
    dp(lena,lenb);
    cout<<f[lena][lenb];
    return 0;
}
View Code

 

 
posted @ 2021-02-02 23:07  _Famiglistimo  阅读(75)  评论(0编辑  收藏  举报