题目地址:
https://leetcode.com/problems/edit-distance/
给定两个字符串
s
s
s和
t
t
t,对
s
s
s可以每次进行三种操作其中的一个:
1、将
s
s
s中新加一个字符;
2、将
s
s
s中删除一个字符;
3、将
s
s
s中替换一个字符。
问最少对
s
s
s进行多少次操作可以将其变为
t
t
t。
思路是动态规划。先考虑极端情况,如果 s s s为空串,那么很显然必须也只能向 s s s中添加 t t t中所有字符才可以,也就是进行 l t l_t lt这么多次;如果 t t t为空串,那么就是从 s s s中删掉所有字符,也就是进行 l s l_s ls这么多次。(严格证明是这样的,因为三个变换中只有insert和delete是会改变字符串长度的,所以要将 s s s变为空串的最短路径必然是每一步都将长度减少 1 1 1,所以最少操作次数必然是 s s s的长度)。
接下来考虑一般情况,设
f
[
i
]
[
j
]
f[i][j]
f[i][j]为将
s
s
s中前
i
i
i个字符(从
0
0
0开始计数,前
0
0
0个字符视为空串)变为
t
t
t中前
j
j
j个字符最少需要多少次变换。考虑
f
[
i
]
[
j
]
f[i][j]
f[i][j]的递推关系:
1、若
s
[
i
−
1
]
≠
t
[
j
−
1
]
s[i-1]\ne t[j-1]
s[i−1]=t[j−1],令
a
=
s
[
:
i
−
1
]
a=s[:i-1]
a=s[:i−1],
b
=
t
[
:
j
−
1
]
b=t[:j-1]
b=t[:j−1],考虑
a
a
a变为
b
b
b的过程。设
a
→
a
1
→
a
2
→
.
.
.
→
a
k
→
a
k
+
1
→
.
.
.
→
b
a\to a_1\to a_2\to ...\to a_k\to a_{k+1}\to ...\to b
a→a1→a2→...→ak→ak+1→...→b,其中
a
k
+
1
a_{k+1}
ak+1以及之后的所有字符串末尾都是
s
[
i
−
1
]
s[i-1]
s[i−1],那么可以考虑从
a
k
→
a
k
+
1
a_k\to a_{k+1}
ak→ak+1是执行了什么操作。如果其是添加,那么可以将这一步添加挪到最后一步做,可以产生一个等价的变换操作(还有很多细节在里面,试讨论之。如果
a
k
+
1
a_{k+1}
ak+1之后的操作不是对最后一个字符的操作,或者可以等效替换为不是对最后一个字符的操作,就可以证明这个结论了。如果其是替换最后一个字符,那是多此一举,不可能;如果是添加和删除,则都可以等效替换,例如最后添加
s
[
i
−
1
]
s[i-1]
s[i−1],那么可以相当于是对除了末尾的前面一段添加这个字符,确实可以等效替换的。删除也类似),等价替换之后步数一样,都大于等于
f
[
i
]
[
j
−
1
]
+
1
f[i][j-1]+1
f[i][j−1]+1;如果
a
k
→
a
k
+
1
a_k\to a_{k+1}
ak→ak+1是执行了删除或者替换,那么也可以类似证明,可以放在最后做。所以此时
f
[
i
]
[
j
]
=
min
{
f
[
i
]
[
j
−
1
]
+
1
,
f
[
i
−
1
]
[
j
−
1
]
+
1
,
f
[
i
−
1
]
[
j
]
+
1
}
=
1
+
min
{
f
[
i
]
[
j
−
1
]
,
f
[
i
−
1
]
[
j
−
1
]
,
f
[
i
−
1
]
[
j
]
}
f[i][j]=\min\{f[i][j-1]+1,f[i-1][j-1]+1,f[i-1][j]+1\}=1+\min\{f[i][j-1],f[i-1][j-1],f[i-1][j]\}
f[i][j]=min{f[i][j−1]+1,f[i−1][j−1]+1,f[i−1][j]+1}=1+min{f[i][j−1],f[i−1][j−1],f[i−1][j]}
2、若
s
[
i
−
1
]
=
t
[
j
−
1
]
s[i-1]=t[j-1]
s[i−1]=t[j−1],则说明
s
s
s和
t
t
t最后一个字符相等,那就直接变换前面的字符即可,所以
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
−
1
]
f[i][j]=f[i-1][j-1]
f[i][j]=f[i−1][j−1]。其实我们还需要证明
f
[
i
−
1
]
[
j
−
1
]
≤
1
+
min
{
f
[
i
]
[
j
−
1
]
,
f
[
i
−
1
]
[
j
]
}
f[i-1][j-1]\le 1+\min\{f[i][j-1],f[i-1][j]\}
f[i−1][j−1]≤1+min{f[i][j−1],f[i−1][j]}。先证
f
[
i
−
1
]
[
j
−
1
]
≤
1
+
f
[
i
]
[
j
−
1
]
f[i-1][j-1]\le 1+f[i][j-1]
f[i−1][j−1]≤1+f[i][j−1]。设
a
′
a'
a′和
b
′
b'
b′分别是
a
a
a和
b
b
b去掉最后一个字符得到的字符串。那么
f
[
i
−
1
]
[
j
−
1
]
f[i-1][j-1]
f[i−1][j−1]对应的方案其实就是
a
′
a'
a′变成
b
′
b'
b′的方案。考虑这样一种方案,先将
a
′
a'
a′后面添加
s
[
i
−
1
]
s[i-1]
s[i−1],然后执行
f
[
i
]
[
j
−
1
]
f[i][j-1]
f[i][j−1]次操作变为
b
′
b'
b′,步数为
1
+
f
[
i
]
[
j
−
1
]
1+f[i][j-1]
1+f[i][j−1],而从
a
′
→
b
′
a'\to b'
a′→b′的最快方案需要
f
[
i
−
1
]
[
j
−
1
]
f[i-1][j-1]
f[i−1][j−1]步,所以有
f
[
i
−
1
]
[
j
−
1
]
≤
1
+
f
[
i
]
[
j
−
1
]
f[i-1][j-1]\le 1+f[i][j-1]
f[i−1][j−1]≤1+f[i][j−1];再证
f
[
i
−
1
]
[
j
−
1
]
≤
1
+
f
[
i
−
1
]
[
j
]
f[i-1][j-1]\le 1+f[i-1][j]
f[i−1][j−1]≤1+f[i−1][j],与刚才类似,考虑这样一种方案,先将
a
′
a'
a′变为
b
′
+
s
[
i
−
1
]
b'+s[i-1]
b′+s[i−1],然后后面删除
s
[
i
−
1
]
s[i-1]
s[i−1],其需要
1
+
f
[
i
−
1
]
[
j
]
1+f[i-1][j]
1+f[i−1][j]步,所以有
f
[
i
−
1
]
[
j
−
1
]
≤
1
+
f
[
i
]
[
j
−
1
]
f[i-1][j-1]\le 1+f[i][j-1]
f[i−1][j−1]≤1+f[i][j−1]。
综上所述,代码如下:
class Solution {
public:
int minDistance(string s1, string s2) {
int m = s1.size(), n = s2.size();
int f[m + 1][n + 1];
for (int i = 0; i <= m; i++)
for (int j = 0; j <= n; j++) {
if (!i || !j) f[i][j] = i ^ j;
else {
if (s1[i - 1] == s2[j - 1]) f[i][j] = f[i - 1][j - 1];
else {
f[i][j] = min(f[i][j - 1] + 1, f[i - 1][j - 1] + 1);
f[i][j] = min(f[i][j], 1 + f[i - 1][j]);
}
}
}
return f[m][n];
}
};
时空复杂度 O ( m n ) O(mn) O(mn), m m m和 n n n分别为两个字符串长度。