一、动态规划DP 编辑问题
1、不同的子序列 115
采用动态规划来解决子序列问题。有两个字符串,采用二维dp数组,dpij表示0~i-1的s子字符串中含有0 ~j-1的t子字符串的个数。
递推公式分为si - 1 与 tj - 1相等和不相等的情况
CPP
class Solution {
public:
int numDistinct(string s, string t) {
int m = s.size(), n = t.size();
vector<vector<unsigned long long>> dp(m + 1, vector<unsigned long long>(n + 1));
for(int i=0; i<=m; ++i)
dp[i][0] = 1;
for(int i=1; i<=m; ++i){
for(int j=1; j<=n; ++j){
dp[i][j] = dp[i-1][j];
if(s[i-1]==t[j-1])
dp[i][j] += dp[i-1][j-1];
}
}
return dp[m][n];
}
};
由递推公式可知,当前值dpij只与其上方的值和左上方值有关,可以将二维数组优化成一维数组,代码如下
CPP
class Solution {
public:
int numDistinct(string s, string t) {
int m = s.size(), n = t.size();
vector<unsigned long long> dp(n + 1, 0);
dp[0] = 1;
for(int i=1; i<=m; ++i){
int leftup = dp[0];
for(int j=1; j<=n; ++j){
int tmp = dp[j];
if(s[i-1]==t[j-1])
dp[j] += leftup;
leftup = tmp;
}
}
return dp[n];
}
};
2、两个字符串的删除操作 583
相比于上一题,区别就是两个字符串都可以进行删除。
所以在推导递推公式时,当word1i-1==word2j-1, d p i j = d p i − 1 j − 1 dpij = dpi-1j-1 dpij=dpi−1j−1;当word1i-1!=word2j-1, d p i j = m i n ( d p i − 1 j , d p i j − 1 ) + 1 dpij = min(dpi-1j, dpij-1) + 1 dpij=min(dpi−1j,dpij−1)+1,会从删除word1i-1或者word2j-1之后取最小的结果,同时+1(删除操作)。
CPP
class Solution {
public:
int minDistance(string word1, string word2) {
int m = word1.size(), n = word2.size();
vector<vector<int>> dp(m + 1, vector<int>(n + 1));
for(int i=0; i<=m; ++i)
dp[i][0] = i;
for(int i=1; i<=n; ++i)
dp[0][i] = i;
for(int i=1; i<=m; ++i){
for(int j=1; j<=n; ++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[m][n];
}
};
空间复杂度优化,思路和之前提到的一样,采用变量记录左上方的值,代码如下:
CPP
class Solution {
public:
int minDistance(string word1, string word2) {
int m = word1.size(), n = word2.size();
vector<int> dp(n + 1);
for(int i=1; i<=n; ++i)
dp[i] = i;
for(int i=1; i<=m; ++i){
int leftup = dp[0];
dp[0] = i;
for(int j=1; j<=n; ++j){
int tmp = dp[j];
if(word1[i-1]==word2[j-1])
dp[j] = leftup;
else
dp[j] = min(dp[j], dp[j-1]) + 1;
leftup = tmp;
}
}
return dp[n];
}
};
3、编辑距离 72
直接DP,dp数组dpij 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2的最近编辑距离。
递推公式推导,当word1i - 1 == word2j - 1时,表示不需要进行操作, d p i j = d p i − 1 j − 1 dpij = dpi-1j-1 dpij=dpi−1j−1;当word1i - 1 != word2j - 1时,我们可以对word2j-1进行删除(dpij-1),也可以对word1i-1进行删除(dpi-1j), d p i j = d p i − 1 j − 1 dpij = dpi-1j-1 dpij=dpi−1j−1;还有将word1i-1或者word2j-1替换(dpi-1j-1),所以 d p i j = m i n ( d p i − 1 j − 1 , m i n ( d p i − 1 j , d p i j − 1 ) ) + 1 dpij = min(dpi-1j-1, min(dpi-1j, dpij-1)) + 1 dpij=min(dpi−1j−1,min(dpi−1j,dpij−1))+1
CPP
class Solution {
public:
int minDistance(string word1, string word2) {
int m = word1.size(), n = word2.size();
vector<vector<int>> dp(m+1, vector<int>(n+1));
for(int i=1; i<=m; ++i)
dp[i][0] = i; // 执行插入操作即可
for(int i=1; i<=n; ++i)
dp[0][i] = i;
for(int i=1; i<=m; ++i){
for(int j=1; j<=n; ++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], min(dp[i][j-1], dp[i-1][j-1])) + 1;
}
}
}
return dp[m][n];
}
};
空间复杂度优化,优化思路和之前一样,需要注意边界的初始化,代码如下:
CPP
class Solution {
public:
int minDistance(string word1, string word2) {
int m = word1.size(), n = word2.size();
vector<int> dp(n+1);
for(int i=1; i<=n; ++i)
dp[i] = i;
for(int i=1; i<=m; ++i){
int leftup = dp[0];
dp[0] = i;
for(int j=1; j<=n; ++j){
int tmp = dp[j];
if(word1[i-1]==word2[j-1]){
dp[j] = leftup;
}else{
dp[j] = min(dp[j], min(dp[j-1], leftup)) + 1;
}
leftup = tmp;
}
}
return dp[n];
}
};
二、写在后面
字符串的编辑问题需要搞清楚是只能编辑一个字符串,还是两个字符串都可以编辑,这决定了递推公式的写法;同时需要注意初始化问题。另外,二维数组转一维数组较为套路化,需要细心。