动态规划-详解计算字符串的距离/优化编辑器问题- Python和C实现

写在最前:
最近又在忙研究又在忙招聘的,虽然没有太多DDL但是每天搞的精神紧张,睡觉都睡不好,因此好久没有更新博客了。不过今天面试的时候有幸被大佬看了自己的博客,并且略加赞赏,真是好开心啊,学习,更新博客的热情高涨!还是要多回顾多反思多总结,因此打算把最近刷题刷到的几个处理方法比较类似的DP总结一下。

题目来源:
牛客网

题目详情:
Levenshtein 距离,又称编辑距离,指的是两个字符串之间,由一个转换成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。编辑距离的算法是首先由俄国科学家Levenshtein提出的,故又叫Levenshtein Distance。

Ex:
字符串A:abcdefg
字符串B: abcdef

通过增加或是删掉字符”g”的方式达到目的。这两种方案都需要一次操作。把这个操作所需要的次数定义为两个字符串的距离。
要求:
给定任意两个字符串,写出一个算法计算它们的编辑距离。
本题含有多组输入数据。

输入描述:
先输入一个带有通配符的字符串,再输入一个需要匹配的字符串

输出描述:
返回匹配的结果,正确输出true,错误输出false

示例输入1:

abcdefg
abcdef
abcde
abcdf
abcde
bcdef

示例输出1:

1
1
2

解题思路:

因为题目是要求两个字符串的编辑距离,并且根据题目规定,可知对字符串一共有替换,插入,删除这3个操作,并且每个操作都会增加一次操作数,即编辑距离+1。

分析一下不难发现要求的这个编辑距离跟字符串中的任意两个相邻字符是有关联的,用str1和str2表示需要比较的两个字符串,用str1[i]表示字符串str1的第i+1个字符,用str2[j]表示字符串str2的第j+1个字符(符合数组的索引规则),要求str1和str2的编辑距离,则需要比较str1[i]str2[j],并且与str1[i-1]str2[j-1]也有关联,因为如果最后所得的编辑距离并不是由单个字符决定的,而是由比较了整个字符串得到的,因此存在状态转移关系,而是是两个数组的逐位比较,因此我们就可以采用一个二维的数组来用动态规划解决问题。

状态转移方程求解

要用动态规划就必然要找状态转移方程,我们用dp[i][j]表示str1的前i位字符编辑成str2的前j位字符所需要的操作次数,即由str1的前i位字符组成的字符串和由str2的前j位字符组成的字符串的编辑距离。

可以根据str1[i-1]str2[j-1]是否相等来得到dp[i][j]

  • str1[i-1]=str2[j-1]时,需要的操作次数即为dp[i-1][j-1],即不需要对str1的第i位字符和str2的第j位字符做任何操作
  • str1[i-1]!=str2[j-1]时,又可分为以下几种情况:
    • 使用修改操作,此时需要的操作次数为dp[i-1][j-1]+1,即在操作完str1的前i-1位字符和str2的前j-1位字符后,再增加一次修改操作使得str1[i-1]=str2[j-1]
    • 使用删除操作删除字符str1[i-1]或者在字符str2[j-1]的后面添加字符str1[i-1],此时需要的操作数为dp[i-1][j]+1,即操作完str1的前i-2位字符和str2的前j-1位字符后,再增加1次删除操作删除掉字符str1[i-1]或者增加1次插入操作,在字符str2[j-1]后添加字符str1[i-1]
    • 使用删除操作删除字符str2[j-1]或者在字符str1[i-1]的后面添加字符str2[j-1],此时需要的操作数为dp[i][j-1]+1,原理同上

因此我们可以得到如下的状态转移方程:
d p [ i ] [ j ] = { d p [ i − 1 ] [ j − 1 ] , s t r 1 [ i − 1 ] = s t r 2 [ j − 1 ] d p [ i − 1 ] [ j − 1 ] + 1 , 不 等 + 修 改 操 作 d p [ i − 1 ] [ j ] + 1 , 不 等 + 删 除 或 插 入 d p [ i ] [ j − 1 ] + 1 , 不 等 + 删 除 或 插 入 dp[i][j]=\left\{ \begin{aligned} dp[i-1][j-1] &, &str1[i-1]=str2[j-1]\\ dp[i-1][j-1]+1 &, &不等+修改操作\\ dp[i-1][j]+1 &, & 不等+删除或插入\\ dp[i][j-1]+1 &, & 不等+删除或插入\\ \end{aligned} \right. dp[i][j]=dp[i1][j1]dp[i1][j1]+1dp[i1][j]+1dp[i][j1]+1,,,,str1[i1]=str2[j1]+++
因为删除或插入的具体内容太长,我就没写在上面的式子中,看式子上面的文字描述应该不难跟式子对应起来。

边界问题
用动态规划解题需要考虑边界,就是特殊点,因为dp[i][j]表示str1的前i位字符编辑成str2的前j位字符所需要的操作次数,因次dp[i][0]dp[0][j]都是特殊点,这些情况对应的是存在字符串为空的情况,那么这时候对应所需的操作数即为相应的字符串的长度,无论对非空字符串做插入,删除操作都可实现,即:
{ d p [ i ] [ 0 ] = i d p [ 0 ] [ j ] = j \left\{ \begin{aligned} dp[i][0]=i & \\ dp[0][j]= j& \\ \end{aligned} \right. {dp[i][0]=idp[0][j]=j

完整程序实现:

C语言版

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define MAXSIZE 300
#define min(x, y) (x < y ? x : y)

int main(){
    char str1[MAXSIZE] = {'\0'}, str2[MAXSIZE] = {'\0'};
    while(scanf("%s%s", str1, str2) != EOF){
        int i, j, len1 = strlen(str1), len2 = strlen(str2);
        int **dp = (int**)calloc(len1+1, sizeof(int*));
        for(i=0; i<len1+1; i++){
            dp[i] = (int*)calloc(len2+1, sizeof(int));
            dp[i][0] = i;
        }
        for(i=0; i<len2+1; i++)
            dp[0][i] = i;
        for(i=1; i<len1+1; i++){
            for(j=1; j<len2+1; j++){
                int temp = min(dp[i][j-1], dp[i-1][j])+1;
                dp[i][j] = min((str1[i-1] == str2[j-1] ? 0 : 1)+dp[i-1][j-1], temp);
            }
        }
        printf("%d\n", dp[len1][len2]);
        free(dp);
    }
    return 0;
}

Python版:

def editDistance(str1, str2):
    len1, len2 = len(str1), len(str2)
    dp = [[0 for i in range(len2+1)] for j in range(len1+1)]
    for i in range(len1+1):
        dp[i][0] = i
    for j in range(len2+1):
        dp[0][j] = j
    for i in range(1, len1+1):
        for j in range(1, len2+1):
            dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + (str1[i - 1] != str2[j - 1]))
    return dp[-1][-1]
 
while True:
    try:
        print(editDistance(input(), input()))
    except:
        break

写在最后
如果有哪里理解错的地方欢迎大家留言交流,如需转载请标明出处。

如果你没看懂一定是我讲的不好,欢迎留言,我继续努力。

手工码字码图码代码,如果有帮助到你的话留个赞吧,谢谢。

以上。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值