考虑用一个二维dp数组表示所需的最小操作次数。
dp[i][j]表示将word1的前i个字符转换为word2的前j个字符所需要的最少操作次数。
由于操作的顺序对于最后操作的结果没有影响,所以我们假设操作总是从word1的前面字符操作到word1的后面字符。
-
如果word1的第i个字符等于word2的第j个字符,所以对于第i个字符实际上是没有操作的,
那么编辑距离就取决于word1的1 ~ i - 1的字符和word2的1 ~ j - 1的字符,
所以我们得到: dp[i][j] = dp[i - 1][j - 1]
这个方程表示当word1的第i个字符等于word2的第j个字符,编辑距离就是将word1的1~i-1个字符改为
word2的1~j-1的编辑距离。 (至于是怎么编辑的,我们不需要管,我们只需要计算编辑距离就行了,下面同理) -
如果word1的第i个字符和word2的第j个字符不相等。那么有三种操作:
(1)如果要将word1的 1 ~ i 转换为word2的 1 ~ j 的最后一步是:在word1的1 ~ i个字符之后增加一个字符word1[i + 1] (这个字符必然是word2[j]),这就比将word1的1i转换为word2的1j-1的编辑距离多一(这里可能有点难以理解,这么说吧,word1最开始是1i,word2是1j-1,由于word1转换为word2的最后一步是加入一个字符word1[i + 1]或者说是word2[j],所以编辑距离就是word1的1i到word2的1j-1的编辑距离加一!) 得到状态转移方程: 编辑距离dp[i][j] = dp[i][j - 1] + 1
(2) 如果要将word1的1 ~ i 转换为word2的 1 ~ j的最后一步是: 删掉word1的第i个字符,那么编辑距离就是将word1的1~i-1个字符转换为word2的1~j个字符的编辑距离加一(加一这部操作表示删除word1[i]), 因此我们得到状态转移方程: dp[i][j] = dp[i - 1][j] + 1
(3) 如果要将word1的1 ~ i 转换为word2的 1 ~ j的最后一步是: 修改word1的第i个字符,那么编辑距离就是将word1的1i-1个字符转换为word2的1j-1个字符的编辑距离加一。可以这么理解:经过将word1的1i-1个字符转换为word2的1j-1个字符的编辑距离之后,word2多了一个字符word[j],偏偏word1的最后一个字符word[i]和word2[j]不相等,好在我们只需要一步修改操作就可以完成所有转换。 所以状态转移方程是:dp[i][j] = dp[i - 1][j - 1] + 1
有了状态转移方程,只需要再对dp数组的边界做初始化,然后就可以开始递推啦。
边界可以这么确定:假设word1和word2两个字符串其中一个字符串为空,另一个字符串长度为x,那么dp[x][0]或者dp[0][x]显然就是x了。
把长度为x的字符串全部删掉也好,把空字符串加x个字符使得它和另一个字符串相等也好,最少的操作次数必然是x。
这样我们就找到了数组递推边界,开始考虑递推:
从上面的第2点(word1的第i个字符不等于word2的第j个字符)的分析可以发现,dp[i][j]是从dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]
三个状态得到的,而且都是在前一个状态的基础上加一(分别表示添加、删除、修改操作),由于我们的dp数组表示操作的最少次数,
所以我们要取三个前置状态的最小值再+1。
因此当word1的第i个字符不等于word2的第j个字符时,我们得到状态转移方程:dp[i][j] = min(dp[i - 1][j - 1], min(dp[i][j - 1], dp[i - 1][j]) + 1
如果当word1的第i个字符和word2的第j个字符相等呢,这个情况我们可以单独判断,如果相等,就让dp[i][j]等于dp[i - 1][j - 1]就好了。
代码如下:
class Solution {
public:
int minDistance(string word1, string word2) {
int n = word1.size(), 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 i = 1; i <= m; ++i) { //初始化边界
dp[0][i] = i;
}
for(int i = 1; i <= n; ++i) {
for(int j = 1; j <= m; ++j) {
dp[i][j] = min(dp[i - 1][j - 1], min(dp[i][j - 1], dp[i - 1][j])) + 1;
if(word1[i - 1] == word2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
}
}
}
return dp.back().back(); //等价于return dp[n][m];
}
};