提示:DDU,供自己复习使用。欢迎大家前来讨论~
动态规划Part12
题目
题目一: 115.不同的子序列
解题思路:
这道题目要求判断字符串s
是否包含字符串t
作为子序列。这里的子序列指的是,t
的字符可以不连续,但顺序必须保持一致。解题思路使用动态规划,具体步骤如下:
1. 确定dp数组(dp table)以及下标的含义
定义二维数组dp[i][j]
,其中dp[i][j]
表示以s
的前i-1
个字符为结尾的子序列中,出现以t
的前j-1
个字符为结尾的子序列的个数。
2. 确定递推公式
根据s[i - 1]
与t[j - 1]
是否相等,分两种情况:
- 相等:
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]
。因为s[i - 1]
可以匹配t[j - 1]
,也可以不匹配。 - 不相等:
dp[i][j] = dp[i - 1][j]
。因为s[i - 1]
不匹配t[j - 1]
,只能忽略s[i - 1]
。
3. dp数组如何初始化
dp[i][0] = 1
:任何字符串的子序列都至少包含空字符串,所以初始化为1。dp[0][j] = 0
:空字符串s
不能包含任何非空字符串t
作为子序列,所以初始化为0。dp[0][0] = 1
:空字符串是自身的子序列。
4. 确定遍历顺序
遍历顺序为从左到右,从上到下,确保每个dp[i][j]
的值都是基于已经计算过的dp[i-1][j]
和dp[i-1][j-1]
来计算的。
5. 举例推导dp数组
以具体例子(如s = "baegg"
,t = "bag"
)来手动计算dp
数组,验证递推公式的正确性。
代码实现
class Solution {
public:
int numDistinct(string s, string t) {
vector<vector<uint64_t>> dp(s.size() + 1, vector<uint64_t>(t.size() + 1));
for (int i = 0; i <= s.size(); i++) dp[i][0] = 1; // 空字符串的子序列计数为1
for (int j = 1; j <= t.size(); j++) dp[0][j] = 0; // 空s不能包含非空t
for (int i = 1; i <= s.size(); i++) {
for (int j = 1; j <= t.size(); j++) {
if (s[i - 1] == t[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]; // s[i-1]匹配t[j-1]或不匹配
} else {
dp[i][j] = dp[i - 1][j]; // s[i-1]不匹配t[j-1],忽略s[i-1]
}
}
}
return dp[s.size()][t.size()]; // 返回s的所有子序列中包含t的计数
}
};
时间复杂度为O(n*m),空间复杂度也为O(n*m),其中n和m分别是字符串s和t的长度。
题目二:583. 两个字符串的删除操作
解题思路:
这道题目要求找出将两个字符串转换为彼此所需的最少删除次数,可以通过动态规划来解决。以下是详细的解题思路:
1. 确定dp数组(dp table)以及下标的含义
定义二维数组dp[i][j]
,其中dp[i][j]
表示将字符串word1
的前i-1
个字符和字符串word2
的前j-1
个字符转换为彼此所需的最少删除次数。
2. 确定递推公式
根据word1[i - 1]
与word2[j - 1]
是否相等,分两种情况:
- 相等:
dp[i][j] = dp[i - 1][j - 1]
。因为当两个字符相等时,不需要删除这两个字符。 - 不相等:
dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1)
。因为当两个字符不相等时,可以选择删除word1
的一个字符或者删除word2
的一个字符,取两者的最小值。
3. dp数组如何初始化
dp[i][0] = i
:表示word2
为空字符串时,需要删除word1
的前i-1
个字符。dp[0][j] = j
:表示word1
为空字符串时,需要删除word2
的前j-1
个字符。
4. 确定遍历顺序
遍历顺序为从上到下,从左到右,确保每个dp[i][j]
的值都是基于已经计算过的dp[i-1][j]
和dp[i][j-1]
来计算的。
5. 举例推导dp数组
以具体例子(如word1 = "sea"
,word2 = "eat"
)来手动计算dp
数组,验证递推公式的正确性。
代码实现
class Solution {
public:
int minDistance(string word1, string word2) {
vector<vector<int>> dp(word1.size() + 1, vector<int>(word2.size() + 1));
for (int i = 0; i <= word1.size(); i++) dp[i][0] = i;
for (int j = 0; 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, dp[i][j - 1] + 1);
}
}
}
return dp[word1.size()][word2.size()];
}
};
时间复杂度为O(n*m),空间复杂度也为O(n*m),其中n和m分别是字符串word1和word2的长度。
动态规划二(求最长公共子序列)
另一种解法是求两个字符串的最长公共子序列(LCS),然后用两个字符串的总长度减去最长公共子序列的长度的两倍,即为最少删除次数。
代码实现
class Solution {
public:
int minDistance(string word1, string 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()-dp[word1.size()][word2.size()]*2;
}
};
时间复杂度为O(n*m),空间复杂度为O(n*m)。
题目三:72. 编辑距离
解题思路
编辑距离问题是一个经典的动态规划问题,用于计算两个字符串之间的最小编辑操作次数,使得一个字符串可以转换成另一个字符串。以下是详细的解题思路:
1. 确定dp数组(dp table)以及下标的含义
定义二维数组dp[i][j]
,其中dp[i][j]
表示以下标i-1
为结尾的字符串word1
和以下标j-1
为结尾的字符串word2
之间的最小编辑距离。
2. 确定递推公式
根据word1[i - 1]
与word2[j - 1]
是否相等,分两种情况:
- 相等:不需要任何编辑操作,即
dp[i][j] = dp[i - 1][j - 1]
。 - 不相等:需要进行编辑操作,可能的操作包括:
- 删除
word1
的一个字符:dp[i - 1][j] + 1
- 删除
word2
的一个字符:dp[i][j - 1] + 1
- 替换
word1
的一个字符:dp[i - 1][j - 1] + 1
- 选择最小操作次数:
dp[i][j] = min({dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]}) + 1
- 删除
3. dp数组如何初始化
dp[i][0] = i
:表示将word1
的前i-1
个字符转换为一个空字符串需要删除i
次。dp[0][j] = j
:表示将一个空字符串转换为word2
的前j-1
个字符需要添加j
次。
4. 确定遍历顺序
遍历顺序为从上到下,从左到右,确保每个dp[i][j]
的值都是基于已经计算过的dp[i-1][j]
、dp[i][j-1]
和dp[i-1][j-1]
来计算的。
5. 举例推导dp数组
以具体例子(如word1 = "horse"
,word2 = "ros"
)来手动计算dp
数组,验证递推公式的正确性。
代码实现
class Solution {
public:
int minDistance(string word1, string 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 = 0; 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], dp[i - 1][j], dp[i][j - 1]}) + 1;
}
}
}
return dp[word1.size()][word2.size()];
}
};
时间复杂度为O(n*m),空间复杂度也为O(n*m),其中n和m分别是字符串word1和word2的长度。
编辑距离总结篇
- 判断子序列:判断一个字符串是否为另一个字符串的子序列,只涉及删除操作。
- 不同的子序列:计算一个字符串在另一个字符串子序列中出现的次数,涉及删除操作。
- 两个字符串的删除操作:计算使两个字符串相同的最少删除次数,涉及两个字符串的删除操作。
编辑距离问题
问题定义:给定两个字符串word1
和word2
,计算将word1
转换成word2
的最少操作数,操作包括插入、删除和替换。