这个题也是离谱。。。主要是要把状态定义出来并且把状态转移方程写出来太困难了,不过不要紧,这篇题解的目的就是为了给第一次做这个题的人理清思路、给复习的人(疑似只有我)快速回忆思路用的,因此会讲清楚这个题的思路。
首先,字符串有关的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替换一个字符。所以实际上可以只用三种操作来描述:
考虑 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)