本题和动态规划:115.不同的子序列?相比,其实就是两个字符串都可以删除:
dp[i][j]:以i-1结尾的word1和j-1结尾的word2的最小删除数;
递推:
两个字符相等时,说明两个字符串都不需要删除,dp[i][j]=dp[i-1][j-1];
两个字符不相等时,有三种可能,要么删除第一个字符串,要么删除第二个字符串,要么两个都删除;因此是三项的最小值,但是注意dp[i-1][j-1]+2=dp[i-1][j]+1,所以可以简化为两项:
dp[i][j]=min(dp[i][j-1]+1,dp[i-1][j]+1);
初始化:
dp[0][j]表示空字符串和第二个字符串相等的删除数,因此等于j,dp[i][0]同理;
遍历顺序:从上到下,从左到右
详细代码如下:
class Solution {
public:
int minDistance(string word1, string word2) {
//dp:以i-1为结尾的word1和以j-1为结尾的word2的最小删除数字
vector<vector<int>>dp(word1.size()+1,vector<int>(word2.size()+1,0));
for(int i=0;i<=word1.size();i++) dp[i][0]=i;
for(int j=1;j<=word2.size();j++) dp[0][j]=j;
for(int i=1;i<=word1.size();i++)
{
for(int j=1;j<=word2.size();j++)
{
if(word1[i-1]==word2[j-1]) dp[i][j]=dp[i-1][j-1];
else dp[i][j]=min(dp[i-1][j],dp[i][j-1])+1;
}
}
return dp[word1.size()][word2.size()];
}
};
另一种思路:求两个字符串的删除最小数等于两个字符串的总长度减去两倍的公共最长子序列,转换后详细代码如下:
class Solution {
public:
int minDistance(string word1, string word2) {
//dp:以i-1为结尾的word1和以j-1为结尾的word2的最长公共子序列
vector<vector<int>>dp(word1.size()+1,vector<int>(word2.size()+1,0));
for(int i=1;i<=word1.size();i++)
{
for(int j=1;j<=word2.size();j++)
{
if(word1[i-1]==word2[j-1]) dp[i][j]=dp[i-1][j-1]+1;
else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
return word1.size()+word2.size()-2*dp[word1.size()][word2.size()];
}
};
最终我们迎来了编辑距离这道题目,详细分析:
1. 确定dp数组(dp table)以及下标的含义
dp[i][j] 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]。
2. 确定递推公式
在确定递推公式的时候,首先要考虑清楚编辑的几种操作,整理如下:
if (word1[i - 1] == word2[j - 1])
不操作
if (word1[i - 1] != word2[j - 1])
增
删
换
也就是如上4种情况。
if (word1[i - 1] == word2[j - 1])
?那么说明不用任何编辑,dp[i][j]
?就应该是?dp[i - 1][j - 1]
,即dp[i][j] = dp[i - 1][j - 1];
if (word1[i - 1] != word2[j - 1])
,此时就需要编辑了,如何编辑呢?
dp[i][j] = dp[i - 1][j] + 1;
dp[i][j] = dp[i][j - 1] + 1;
word2添加一个元素,相当于word1删除一个元素,例如?word1 = "ad" ,word2 = "a"
,word1
删除元素'd'
?和?word2
添加一个元素'd'
,变成word1="a", word2="ad"
, 最终的操作数是一样! dp数组如下图所示意的: a a d
+-----+-----+ +-----+-----+-----+
| 0 | 1 | | 0 | 1 | 2 |
+-----+-----+ ===> +-----+-----+-----+
a | 1 | 0 | a | 1 | 0 | 1 |
+-----+-----+ +-----+-----+-----+
d | 2 | 1 |
+-----+-----+
操作三:替换元素,word1
替换word1[i - 1]
,使其与word2[j - 1]
相同,此时不用增删加元素。
可以回顾一下,if (word1[i - 1] == word2[j - 1])
的时候我们的操作 是?dp[i][j] = dp[i - 1][j - 1]
?
那么只需要一次替换的操作,就可以让 word1[i - 1] 和 word2[j - 1] 相同。
所以?dp[i][j] = dp[i - 1][j - 1] + 1;
综上,当?if (word1[i - 1] != word2[j - 1])
?时取最小的,即:dp[i][j] = min({dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]}) + 1;
递归公式代码如下:
if (word1[i - 1] == word2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
}
else {
dp[i][j] = min({dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]}) + 1;
}
3. dp数组如何初始化
再回顾一下dp[i][j]的定义:
dp[i][j] 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]。
dp[i][0] :以下标i-1为结尾的字符串word1,和空字符串word2,最近编辑距离为dp[i][0]。
那么dp[i][0]就应该是i,对word1里的元素全部做删除操作,即:dp[i][0] = i;
同理dp[0][j] = j;
详细代码如下:
class Solution {
public:
int minDistance(string word1, string word2) {
//以i-1结尾的word1和j-1结尾的word2最小的操作数
//核心思路,删除word1操作等同于增加word2字符操作
vector<vector<int>>dp(word1.size()+1,vector<int>(word2.size()+1,0));
for(int i=0;i<=word1.size();i++) dp[i][0]=i;
for(int j=1;j<=word2.size();j++) dp[0][j]=j;
for(int i=1;i<=word1.size();i++)
{
for(int j=1;j<=word2.size();j++)
{
if(word1[i-1]==word2[j-1]) dp[i][j]=dp[i-1][j-1];
else
{
dp[i][j]=min(dp[i-1][j]+1,min(dp[i][j-1]+1,dp[i-1][j-1]+1));
}
}
}
return dp[word1.size()][word2.size()];
}
};