题目概述
题目链接:点我做题
题解
这个题也是离谱。。。主要是要把状态定义出来并且把状态转移方程写出来太困难了,不过不要紧,这篇题解的目的就是为了给第一次做这个题的人理清思路、给复习的人(疑似只有我)快速回忆思路用的,因此会讲清楚这个题的思路。
首先,字符串有关的dp题目,状态一般都设置成从第一个字符到第i个字符的反映问题的东西,这里有两个字符串,因此考虑设置二维数组
f
(
i
,
j
)
f(i,j)
f(i,j),看看题意是求
w
o
r
d
1
word1
word1到
w
o
r
d
2
word2
word2的编辑距离,那么
f
(
i
,
j
)
f(i,j)
f(i,j)就代表考虑
w
o
r
d
1
word1
word1的前i个字符变到
w
o
r
d
2
word2
word2的前j个字符的编辑距离。假设
w
o
r
d
1
word1
word1的长度是n,
w
o
r
d
2
word2
word2的长度是m,那么我们要的答案就是
f
(
n
,
m
)
f(n,m)
f(n,m).
建出状态来以后,要考虑的就是状态转移方程,就要回到题目中允许的三种操作:插入一个字符、删除一个字符、替换一个字符。再考虑一个对称性的问题,word1变到word2的编辑距离和word2变到word1的编辑距离显然是相等的,因为每一步都是可逆的,那么我们求编辑距离可以同时对word1操作、对word2操作,当word1和word2相同的时候,总操作次数就是编辑距离。
如果上面这一大串没看懂,没关系,意思就是说word1和word2可以双向奔赴,思考时不必保持word2不变让word1变成word2。
破处了这个枷锁以后,我们再来看这个题,显然word1和word2同时都能操作的话,
3
∗
2
=
6
3*2=6
3∗2=6,就有6种操作了,但是考虑到对称性:word1插入一个字符等价于word2删除一个字符、word2插入一个字符等价于word1删除一个字符、word1替换一个字符等价于word2替换一个字符。所以实际上可以只用三种操作来描述:
- w o r d 1 word1 word1插入一个字符:
- w o r d 2 word2 word2插入一个字符
-
w
o
r
d
1
word1
word1替换一个字符
有了这些操作我们再来联想状态转移方程,既然每次只能变一个字符,那么 f ( i , j ) f(i,j) f(i,j)一定是由 f ( i − 1 , j ) 、 f ( i , j − 1 ) 、 f ( i − 1 , j − 1 ) f(i - 1,j)、f(i,j-1)、f(i-1,j-1) f(i−1,j)、f(i,j−1)、f(i−1,j−1)这三个状态转移来的。
考虑
f
(
i
−
1
,
j
)
f(i-1,j)
f(i−1,j)变成
f
(
i
,
j
)
f(i,j)
f(i,j),怎么变呢,
f
(
i
−
1
,
j
)
f(i-1,j)
f(i−1,j)的意思是由
w
o
r
d
2
word2
word2的前j个字符变成
w
o
r
d
1
word1
word1的前i-1个字符的编辑距离,那然后再让此时的
w
o
r
d
1
word1
word1的前i-1个字符再插入一个
w
o
r
d
1
word1
word1的第i个字符不就行了.
因此此时编辑距离等于由word2的前j个字符变为word1的前i-1个字符的编辑距离加上word1的前i-1个字符再插入word1的第i个字符这一操作的编辑距离,因此如果由
f
(
i
−
1
,
j
)
f(i-1,j)
f(i−1,j)递推到
f
(
i
,
j
)
f(i,j)
f(i,j),编辑距离就是
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
,
j
−
1
)
f(i,j - 1)
f(i,j−1)变成
f
(
i
,
j
)
f(i,j)
f(i,j),同上,先让word1的前i个字符变为word2的前j-1个字符,然后让word2的前j-1个字符执行插入word2的第j个字符的操作就可以了,因此此时的编辑距离就等于由word1的前i个字符变为word2的前j-1个字符的编辑距离
f
(
i
,
j
−
1
)
f(i,j-1)
f(i,j−1)再加上插入word2的第j个字符这一操作的编辑距离1,即
f
(
i
,
j
)
=
f
(
i
,
j
−
1
)
+
1
f(i,j)=f(i,j-1)+1
f(i,j)=f(i,j−1)+1
考虑
f
(
i
−
1
,
j
−
1
)
f(i-1,j-1)
f(i−1,j−1)变成
f
(
i
,
j
)
f(i,j)
f(i,j),这里也只剩一种操作了,就是替换,首先让word1的前i-1个字符变为word2的前j-1个字符,然后将word1的第i个字符替换为word2的第j个字符(如果word1的第i个字符和word2的第j个字符相同则不用替换),这样就完成了由word1的前i个字符变为word2的前j-1个字符。
此时编辑距离就等于由word1的前i-1个字符变为word2的前j-1个字符的编辑距离
f
(
i
−
1
,
j
−
1
)
f(i-1,j-1)
f(i−1,j−1)再加上word1的第i个字符替换为word2的第j个字符这一操作的编辑距离1(如果word1的第i个字符和word2的第j个字符相等就不用加上这一步了),
i
f
(
w
o
r
d
1
[
i
−
1
]
=
=
w
o
r
d
2
[
j
−
1
]
)
,
f
(
i
,
j
)
=
f
(
i
−
1
,
j
−
1
)
e
l
s
e
,
f
(
i
,
j
)
=
f
(
i
−
1
,
j
−
1
)
+
1
if (word1[i - 1] == word2[j - 1]),f(i,j) = f(i - 1,j - 1)\\ else,f(i,j) = f(i - 1,j - 1) + 1
if(word1[i−1]==word2[j−1]),f(i,j)=f(i−1,j−1)else,f(i,j)=f(i−1,j−1)+1
由于求的是最小编辑距离,因此f(i,j) = min(1.,2.,3.).
接下来考虑初始条件,如果一个word1的前0个字符(也就是个空字符)变为word2的前j个字符,显然只有把word2的j个字符依次插入这一种途径了,因为编辑距离就是执行j次插入的编辑距离:
f
(
0
,
j
)
=
j
f(0,j) = j
f(0,j)=j;
同理,如果把word2的前0个字符变为word1的前i个字符,编辑距离为
f
(
i
,
0
)
=
i
f(i,0)=i
f(i,0)=i;
这个dp优化不了空间复杂度,因为状态转移时同时有
f
(
i
−
1
,
j
−
1
)
f(i - 1,j - 1)
f(i−1,j−1)和
f
(
i
,
j
−
1
)
f(i,j-1)
f(i,j−1)这两项,这要求dp[j - 1]
同时储存上一行的
f
(
i
−
1
,
j
−
1
)
f(i - 1,j - 1)
f(i−1,j−1)和本行的
f
(
i
,
j
−
1
)
f(i,j - 1)
f(i,j−1),前者要求内层循环从大到小遍历,后者要求内层循环从小到大遍历,矛盾。
代码:
class Solution {
public:
int minDistance(string word1, string word2)
{
/*
f(i,j) 考虑word1的前i个字符变成word2的前j个字符的编辑距离
若word1的长度为n word2的长度为m
答案就是f(n,m)
首先要明确 六种操作可以归类为3种操作
对word1插入一个字符等价于对word2删除一个字符
对word2插入一个字符等价于对word1删除一个字符
对word1替换一个字符等价于对word2替换一个字符
三种操作:
1.对word1插入一个字符
2.对word2插入一个字符
3.对word1替换一个字符
然后来分析状态转移方程
1.f(i - 1,j)变到f(i,j),
这个过程是word2的前j个字符变到word1的前i-1个字符,然后再让word1的前i-1个字符插入第i个字符
显然编辑距离是由word2的前j个字符变为word1的前i-1个字符的编辑距离再加word1后面再插入一个字符的1,
即f(i,j) = f(i - 1,j) + 1
2.f(i,j - 1)变到f(i,j),
这个过程是word1的前i个字符变成word2的前j-1个字符,然后再让word2的前j-1个字符插入第j个字符
显然编辑距离是前一步的编辑距离再加1,
即f(i,j) = f(i,j - 1) + 1
3.f(i - 1, j - 1)变到f(i,j),
这个过程是修改word1的第i个字符使其与word2的第j个字符相等,然后让word1的前i-1个字符变为word2的前j-1个字符
如果本来word1[i - 1] == word2[j - 1] 那就不用做这一次又该操作了
所以f(i,j) = f(i - 1, j - 1), if word1[i - 1] == word2[j - 1]
f(i,j) = f(i - 1, j - 1) + 1, else
只有这三种变化方法 所以f(i,j) = min(1.2.3.)
初始条件 f(i, 0) = i f(0,j) = j
*/
int n = word1.size();
int m = word2.size();
vector<vector<int>> dp(n + 1, vector<int>(m + 1));
for (int i = 1; i <= n; ++i)
{
dp[i][0] = i;
}
for (int j = 1; j <= m; ++j)
{
dp[0][j] = j;
}
for (int i = 1; i <= n; ++i)
{
for (int j = 1; j <= m; ++j)
{
if (word1[i - 1] != word2[j - 1])
{
dp[i][j] = mymin(dp[i - 1][j],
dp[i][j - 1], dp[i - 1][j - 1]) + 1;
}
else
{
dp[i][j] = mymin(dp[i - 1][j] + 1,
dp[i][j - 1] + 1, dp[i - 1][j - 1]);
}
}
}
return dp[n][m];
}
int mymin(int a, int b, int c)
{
if (a < b)
{
if (a < c)
{
return a;
}
else
{
return c;
}
}
/*a > b*/
else
{
if (b > c)
{
return c;
}
else
{
return b;
}
}
}
};
时间复杂度:
O
(
m
∗
n
)
O(m*n)
O(m∗n)
空间复杂度:
O
(
m
∗
n
)
O(m*n)
O(m∗n)