不同的子序列
dp[i][j]:以i-1为结尾的s的子序列中出现以j-1为结尾的t的个数。
递推公式上,我们需要考虑s[i-1] == t[j-1]和s[i-1]!=t[j-1]两种情况
当s[i-1] == t[j-1]时,有两种情形,1是使用s[i-1]来匹配,如s:bagg和t:bag,s[2] = s[3] = 'g',可以使用s[0]、s[1]、s[2]来实现t,也能用s[0]、S[1]、S[3]来实现t,所以当s[i-1] == t[j-1]时,
dp[i][j] = dp[i-1][j] + dp[i-1][j-1]。
当s[i-1]!=t[j-1]时,dp[i][j] = dp[i-1][j],即不考虑使用s[i-1]来匹配(相当于模拟在s中删除这个元素)
dp数组由二维数组当前ij的左上和上方推导而来,因此需要初始化的值有dp[0][0],dp[i][0]。
dp[i][0]表示以i-1为结尾的s可以删除元素,出现空字符串的个数。因此dp[i][0]为0.
dp[0][j]表示空字符串s可以随便删除元素,出现以j-1为结尾的字符串t的个数。dp[0][j]为0
dp[0][0]表示空字符串s可以删除0个元素变成空字符串t.dp[0][0]为1.
由于值由左上和上方得到,所以遍历顺序为从上到下,再从左到右。
最后返回dp[s.size()][t.size()]
class Solution {
public:
int numDistinct(string s, string t) {
// 创建一个二维向量 dp,用于存储动态规划的状态值
// dp[i][j] 表示 s 的前 i 个字符和 t 的前 j 个字符的子序列出现次数
// 初始化 dp 的大小为 (s.size()+1) x (t.size()+1),值全部为 0
vector<vector<uint64_t>> dp(s.size() + 1, vector<uint64_t>(t.size() + 1, 0));
// 初始化 dp 数组的边界值
// dp[i][0] 表示 s 的前 i 个字符和空字符串的子序列出现次数,即 1
// dp[0][j] 表示空字符串和 t 的前 j 个字符的子序列出现次数,即 0
for (int i = 0; i < s.size(); i++) {
dp[i][0] = 1;
}
for (int j = 1; j < t.size(); j++) {
dp[0][j] = 0;
}
// 双层循环遍历 s 和 t
for (int i = 1; i <= s.size(); i++) { // i 从 1 开始,直到 s.size()
for (int j = 1; j <= t.size(); j++) { // j 从 1 开始,直到 t.size()
// 如果 s 的第 i 个字符和 t 的第 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 个字符的子序列
// 加上 s 的第 i 个字符,形成 s 的前 i 个字符和 t 的前 j 个字符的子序列
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
} else {
// 如果字符不同,则 dp[i][j] 等于 dp[i-1][j]
// 这表示我们无法将 s 的前 i-1 个字符与 t 的前 j-1 个字符的子序列
// 加上 s 的第 i 个字符,形成 s 的前 i 个字符和 t 的前 j 个字符的子序列
dp[i][j] = dp[i - 1][j];
}
}
}
// 返回 dp[s.size()][t.size()]
return dp[s.size()][t.size()];
}
};
算法的时间复杂度为O(m*n),空间复杂度为O(m*n)
两个字符串的删除操作
583. 两个字符串的删除操作 - 力扣(LeetCode)
寻找最长的公共子序列,然后删去与公共子序列不同的部分。
class Solution {
public:
int minDistance(string word1, string word2) {
// 创建一个二维向量 dp,用于存储动态规划的状态值
// 初始化 dp 的大小为 (word1.size()+1) x (word2.size()+1),值全部为 0
vector<vector<int>> dp(word1.size() + 1, vector<int>(word2.size() + 1, 0));
// 双层循环遍历 word1 和 word2
for (int i = 1; i <= word1.size(); i++) { // i 从 1 开始,直到 word1.size()
for (int j = 1; j <= word2.size(); j++) { // j 从 1 开始,直到 word2.size()
// 如果 word1 的第 i 个字符和 word2 的第 j 个字符相同
if (word1[i - 1] == word2[j - 1]) {
// 则 dp[i][j] 等于 dp[i-1][j-1] 加 1
// 这表示我们可以将 word1 的前 i-1 个字符转换为 word2 的前 j-1 个字符
// 然后添加 word1 的第 i 个字符,即 word1 的第 i 个字符和 word2 的第 j 个字符相同
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
// 如果字符不同,则 dp[i][j] 等于 dp[i-1][j] 和 dp[i][j-1] 中的较大值
// 这表示我们可以在不替换字符的情况下插入或删除一个字符,或者替换一个字符
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
// 返回 length - 2*dp[word1.size()][word2.size()],即编辑距离
return length - 2 * dp[word1.size()][word2.size()];
}
};
算法的时间复杂度为O(m*n),空间复杂度为O(m*n)
编辑距离
dp[i][j]表示将word1前i个字符转换为word2前j个字符所需的最少操作次数。
当word1[i-1] == word2[j-1]时,dp[i][j] = dp[i-1][j-1],由于当前两个字符相同,则转换的最少操作次数和之前相同。
当word1[i-1]!=word2[j-1]时,给出了三个操作,分别是添加、删除和修改,其中,添加和删除是一体两面的,word1的添加等同于word2的删除,反之亦然,此时dp[i][j] = dp[i-1][j] + 1、dp[i][j] = dp[i][j-1] + 1,修改即将word1[i-1]改为word2[j-1],此时,dp[i][j] = dp[i-1][j-1] + 1。
dp[i][j] = min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])+1。
dp[i][0]表示将word1变成空字符需要几步,dp[i][0] = i;
dp[0][j]表示将word2变成空字符需要几步,dp[0][j] = j;
dp[0][0] = 0;
从左到右从上到下进行遍历。
最后返回dp[word1.size()][word2.size()]。
class Solution {
public:
int minDistance(string word1, string word2) {
// 创建一个二维向量 dp,用于存储动态规划的状态值
// dp[i][j] 表示将 word1 的前 i 个字符转换为 word2 的前 j 个字符所需的最少编辑操作次数
vector<vector<int>> dp(word1.size() + 1, vector<int>(word2.size() + 1, 0));
// 初始化 dp 数组的边界值
// dp[i][0] 表示将 word1 的前 i 个字符转换为空字符串所需的最少编辑操作次数,即 i
// dp[0][j] 表示将空字符串转换为 word2 的前 j 个字符所需的最少编辑操作次数,即 j
for (int i = 0; i <= word1.size(); i++) {
dp[i][0] = i;
}
for (int j = 0; j <= word2.size(); j++) {
dp[0][j] = j;
}
// 双层循环遍历 word1 和 word2
for (int i = 1; i <= word1.size(); i++) { // i 从 1 开始
for (int j = 1; j <= word2.size(); j++) { // j 从 1 开始
// 如果 word1 的第 i 个字符和 word2 的第 j 个字符相同
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];
} else {
// 如果字符不同,则 dp[i][j] 等于 dp[i-1][j-1]、dp[i-1][j] 和 dp[i][j-1] 中的最小值加 1
// 这表示我们可以在不替换字符的情况下插入或删除一个字符,或者替换一个字符
dp[i][j] = min({dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]}) + 1;
}
}
}
// 返回 dp[word1.size()][word2.size()],即编辑距离
return dp[word1.size()][word2.size()];
}
};
算法的时间复杂度为O(m*n),空间复杂度为O(m*n)