编辑距离是指两个子串之间,由一个转成另一个所需要的最少编辑操作次数。允许的编辑操作包括:
-
将一个字符替换成另一个字符
-
插入一个字符
-
删除一个字符
这是常见的求两个字符编辑距离的题目描述,被称为莱文斯坦距离,又称为Levenshtein距离,是俄罗斯科学家莱温斯坦在1965年提出来的概念。 – 来自于维基百科
定义
假设两个字符串分别为A,B,长度分别为
n
=
∣
A
∣
n=\left| A \right|
n=∣A∣和
m
=
∣
B
∣
m=\left| B \right|
m=∣B∣,那么它们的莱文斯坦距离为
l
e
v
A
,
B
(
n
,
m
)
lev_{A,B} \left(n, m \right)
levA,B(n,m)。公式定义为:
l
e
v
A
,
B
(
i
,
j
)
=
{
m
a
x
(
i
,
j
)
i
f
m
i
n
(
i
,
j
)
=
0
m
i
n
{
l
e
v
A
,
B
(
i
−
1
,
j
)
+
1
l
e
v
A
,
B
(
i
,
j
−
1
)
+
1
l
e
v
A
,
B
(
i
−
1
,
j
−
1
)
+
I
A
i
≠
B
j
o
t
h
e
r
s
i
z
e
lev_{A,B} \left(i, j \right) = \left\{\begin{matrix} max \left( i, j \right) & if \; min\left( i, j \right)=0 \\ min \left\{\begin{matrix} lev_{A,B} \left( i -1 , j \right) + 1 \\ lev_{A,B} \left( i , j -1 \right) + 1 \\ lev_{A,B} \left( i -1 , j - 1 \right) + I_{A_{i} \neq B_{j}} \end{matrix}\right. & othersize \end{matrix}\right.
levA,B(i,j)=⎩⎪⎪⎨⎪⎪⎧max(i,j)min⎩⎨⎧levA,B(i−1,j)+1levA,B(i,j−1)+1levA,B(i−1,j−1)+IAi=Bjifmin(i,j)=0othersize
其中
l
e
v
A
,
B
(
i
,
j
)
lev_{A,B} \left( i, j \right)
levA,B(i,j)表示的字符串A的前
i
i
i个字符与字符串B的前
j
j
j个字符之间的莱文斯坦距离;
l
e
v
A
,
B
(
i
−
1
,
j
)
lev_{A,B} \left( i - 1, j \right)
levA,B(i−1,j) 表示删除字符
l
e
v
A
,
B
(
i
,
j
−
1
)
lev_{A,B} \left( i, j - 1 \right)
levA,B(i,j−1) 表示插入字符
l
e
v
A
,
B
(
i
−
1
,
j
−
1
)
lev_{A,B} \left( i -1 , j - 1 \right)
levA,B(i−1,j−1) 表示字符替换
I
A
i
≠
B
j
I_{A_{i} \neq B_{j}}
IAi=Bj是指示函数,
A
i
A_{i}
Ai与
B
j
B_{j}
Bj相等时,取值为0,否则,取值为1。
上述是数学描述,真正代码实现时,由上述的公式可以很清晰地列出动态规划方程。假设字符串 A = “ h o r s e ” A=“horse” A=“horse”, n = 5 n=5 n=5;字符串 B = “ r o s ” B=“ros” B=“ros”。设编辑数组为dp,数据初始化如下表形式。
A/B | 0 | 1 | 2 | 3 |
---|---|---|---|---|
0 | 0 | 1 | 2 | 3 |
1 | 1 | |||
2 | 2 | |||
3 | 3 | |||
4 | 4 | |||
5 | 5 |
表示的是一个空字符串与字符串 A A A或字符串 B B B子串之间的编辑距离。
初始化之后,就需要计算字符串 A A A中第 i i i个字符 A i A_{i} Ai与字符串B中第 j j j个字符 B j B_{j} Bj的编辑距离了。比较 A i A_{i} Ai 与 B j B_{j} Bj:
-
如果 A i A_{i} Ai 与 B j B_{j} Bj相等,那么 d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] dp[i][j] = dp[i-1][j-1] dp[i][j]=dp[i−1][j−1],无需任何操作;
-
如果 A i A_{i} Ai 与 B j B_{j} Bj不相等,那么就要比较三种操作(删除,插入和替换)哪一种更优惠,那么用公式表示就是: d p [ i ] [ j ] = 1 + m i n ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] , d p [ i − 1 ] [ j − 1 ] ) dp[i][j] = 1 + min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) dp[i][j]=1+min(dp[i−1][j],dp[i][j−1],dp[i−1][j−1])。
计算完之后,我们所给的例子字符串 A = “ h o r s e ” A=“horse” A=“horse”和 B = “ r o s ” B=“ros” B=“ros”最后生成的dp数组如下表所示,我们将操作所在的位置加粗显示。 d p [ 5 ] [ 3 ] = 3 dp[5][3] = 3 dp[5][3]=3,字符串A向B至少需要三步,分别是:第一步:horse -> rorse (将 ‘h’ 替换为 ‘r’);第二步:rorse -> rose (删除 ‘r’);第三步:rose -> ros (删除 ‘e’)。
A/B | 0 | 1 | 2 | 3 |
---|---|---|---|---|
0 | 0 | 1 | 2 | 3 |
1 | 1 | 1 | 2 | 3 |
2 | 2 | 2 | 1 | 2 |
3 | 3 | 2 | 2 | 2 |
4 | 4 | 3 | 3 | 2 |
5 | 5 | 4 | 4 | 3 |
class Solution {
public:
int minDistance(string word1, string word2) {
int len_1 = word1.length();
int len_2 = word2.length();
vector<vector<int>> dp(len_1+1, vector<int>(len_2+1, 0));
for(int i=0;i<len_1+1;i++){
dp[i][0] = i;
}
for(int j=0;j<len_2+1;j++){
dp[0][j] = j;
}
for(int i=0;i<len_1;i++){
for(int j=0;j<len_2;j++){
if(word1[i] == word2[j]){
dp[i+1][j+1] = dp[i][j];
}else{
int min_tmp = dp[i][j+1] < dp[i+1][j] ? dp[i][j+1] : dp[i+1][j];
min_tmp = min_tmp < dp[i][j] ? min_tmp : dp[i][j];
dp[i+1][j+1] = min_tmp + 1;
}
}
}
return dp[len_1][len_2];
}
};