写在最前:
最近又在忙研究又在忙招聘的,虽然没有太多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[i−1][j−1]dp[i−1][j−1]+1dp[i−1][j]+1dp[i][j−1]+1,,,,str1[i−1]=str2[j−1]不等+修改操作不等+删除或插入不等+删除或插入
因为删除或插入的具体内容太长,我就没写在上面的式子中,看式子上面的文字描述应该不难跟式子对应起来。
边界问题
用动态规划解题需要考虑边界,就是特殊点,因为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
写在最后
如果有哪里理解错的地方欢迎大家留言交流,如需转载请标明出处。
如果你没看懂一定是我讲的不好,欢迎留言,我继续努力。
手工码字码图码代码,如果有帮助到你的话留个赞吧,谢谢。
以上。