动态规划
编辑距离 Like 总结
代码随想录
子序列判断
编辑距离问题,本体的本质就是求 s 与 t 的最长公共子序列,只不过这个 s 不可删除
1. dp[i][j]
表示 i-1, j-1
为结尾的 s/t 字符串的最长公共子序列的长度
2. 递推公式类似求最长公共子序列固定 s 不变,只删除 t(dp[i][j-1]
)
if(s[i-1] == t[j-1]) dp[i][j] = dp[i-1][j-1]+1
else dp[i][j] = dp[i][j-1]
不同子序列
多少种删除 s 的方式让其变成 t
1. 定义二维 dp 数组,dp[i][j] 以i-1 结尾的s与j-1结尾的t的不同子序列个数
2. 递推公式,考虑的是删除 s 使得变成 t,因此 t 不需要动,只考虑 s 的情况 (刚好匹配与-1 匹配)
if(s[i-1] == t[j-1]) dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
不考虑 s[j-1]
的元素
else dp[i][j] = dp[i-1][j]
这里可能有同学不明白了,为什么还要考虑 不用s[i - 1]来匹配,都相同了指定要匹配啊。
例如: s:bagg 和 t:bag ,s[3] 和 t[2]是相同的,但是字符串s也可以不用s[3]来匹配,即用s[0]s[1]s[2]组成的bag。
字符串删除
上一题的加强版本,两个字符都可以删除了
1. 定义 dp 数组,dp[i][j] 以i-1 结尾的s与j-1结尾的t的最小操作步数(删除步数)
2. 递推公式,首先比较都相同(相同不考虑因此不+),然后 s 和 t 分别删除比较
统计的是几步,相同当然不算做步数
if(s[i-1] == t[j-1]) dp[i][j]=dp[i-1][j-1]
删除 i 可以还是删除 j 可以还是同时删除满足相同
else dp[i][j] = min(dp[i-1][j] + 1, dp[i][j-1] + 1, dp[i-1][j-1]+2)
编辑距离
编辑距离问题,之前的组序列都是基于编辑距离而来
1. 定义 dp 数组 i-1 j-1 结尾最少操作次数
2. 递推公式
相同不操作
If (word 1[i-1] == word 2[j-1]) dp[i][j] = dp[i-1][j-1];
不相同可以删除 dp[i-1][j]
可以插入 dp[i][j-1]
可以替换 dp[i-1][j-1]+1
删除 word 1 i 等价于插入 word 2 j,插入 word 2 j 等价于删除 word 1 i
Word 1 变为 word 2 时 word 1 删除就是删除
Word 2 变为 word 1 时删除 word 2 相当于增加了 word 1 一个元素
else dp[i][j] = std:: min (dp[i-1][j]+1, std:: min (dp[i][j-1]+1, dp[i-1][j-1]+1))
题目
115. 不同的子序列 - 力扣(LeetCode)
多少种删除 s 的方式让其变成 t
1. 定义二维 dp 数组,dp[i][j] 以i-1 结尾的s与j-1结尾的t的不同子序列个数
2. 递推公式,考虑的是删除 s 使得变成 t,因此 t 不需要动,只考虑 s 的情况 (刚好匹配与-1 匹配)
if(s[i-1] == t[j-1]) dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
不考虑 s[j-1]
的元素
else dp[i][j] = dp[i-1][j]
3. 初始化要初始化第一行、第一列 i ~ s, j ~ t
dp[0][j]=0
表示 s 是空的,t 不空,s 无法变成 t
dp[i][0]=1
表示 s 不空,t 空,则有一种方法变成 t
4. 遍历顺序从前往后遍历
5. 打印 dp
int numDistinct(string s, string t) {
// 定义dp数组
std::vector<std::vector<uint64_t>> dp(s.size()+1, std::vector<uint64_t>(t.size()+1, 0));
// 初始化dp
for (int i = 0; i < s.size(); ++i) dp[i][0] = 1;
// 遍历dp
for (int i = 1; i <= s.size(); ++i) {
for (int j = 1; j <= t.size(); ++j) {
// s继续删除 是否匹配t
if (s[i-1] == t[j-1]) dp[i][j] = dp[i-1][j-1] + dp[i-1][j];
// t 固定 不能动j
else dp[i][j] = dp[i-1][j];
}
}
return dp[s.size()][t.size()];
}
583. 两个字符串的删除操作 - 力扣(LeetCode)
上一题的加强版本,两个字符都可以删除了
1. 定义 dp 数组,dp[i][j] 以i-1 结尾的s与j-1结尾的t的最小操作步数(删除步数)
2. 递推公式,首先比较都相同(相同不考虑因此不+),然后 s 和 t 分别删除比较
统计的是几步,相同当然不算做步数
if(s[i-1] == t[j-1]) dp[i][j]=dp[i-1][j-1]
删除 i 可以还是删除 j 可以还是同时删除满足相同
else dp[i][j] = min(dp[i-1][j] + 1, dp[i][j-1] + 1, dp[i-1][j-1]+2)
3. 初始化顺序
dp[0][j]=j
删除 t 到空字符串可以匹配最少需要 j 步
dp[i][0]=i
删除 s 到空字符串可以匹配最少需要 i 步
4. 遍历顺序从前往后
5. 打印 dp
另一种思路,求最长公共子序列,删除到相同最后就是最长公共子序列,因此原先-剩下的就是最少操作步数 return s.size()+t.size()-2*dp[s.size()][t.size()]
// 思路1
int minDistance(string word1, string word2) {
// 定义dp i-1 j-1为结尾的最小操作步数
std::vector<std::vector<int>> dp(word1.size()+1, std::vector<int>(word2.size()+1, 0));
// 初始化dp w2 为空 w1删除i次到空, w1为空 w2删除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;
// 遍历do
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] = std::min(dp[i-1][j]+1, std::min(dp[i][j-1]+1, dp[i-1][j-1]+2));
}
}
return dp[word1.size()][word2.size()];
}
// 思路2
int minDistance(string word1, string word2) {
// dp i-1 j-1 结尾的最长公共子序列
vector<vector<int>> dp(word1.size()+1, vector<int> (word2.size()+1, 0));
// 初始化为0 空字符最长公共子序列为0
// 遍历dp
for (int i = 1; i <= word1.size(); ++i) {
for (int j = 1; j <= word2.size(); ++j) {
// 相同最长公共子序列+1
if (word1[i-1] == word2[j-1]) dp[i][j] = dp[i-1][j-1]+1;
// 不同就递补最长公共子序列结果 因为不需要连续 就可以递补
// 如果是连续子序列那么没有else
else dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
}
}
return word1.size() + word2.size() - 2 * dp[word1.size()][word2.size()];
}
72. 编辑距离 - 力扣(LeetCode)
编辑距离问题,之前的组序列都是基于编辑距离而来
1. 定义 dp 数组 i-1 j-1 结尾最少操作次数
2. 递推公式
相同不操作
If (word 1[i-1] == word 2[j-1]) dp[i][j] = dp[i-1][j-1];
不相同可以删除 dp[i-1][j]
可以插入 dp[i][j-1]
可以替换 dp[i-1][j-1]+1
删除 word 1 i 等价于插入 word 2 j,插入 word 2 j 等价于删除 word 1 i
word 1 变为 word 2 时 word 1 删除就是删除
word 2 变为 word 1 时删除 word 2 相当于增加了 word 1 一个元素
else dp[i][j] = std:: min (dp[i-1][j]+1, std:: min (dp[i][j-1]+1, dp[i-1][j-1]+1))
3. 初始化 dp
初始化 dp[0][j] dp[i][0]
dp[0][j]=j
w 1 是空 w 2 不空 w 2 删除 j 个可以相同,或者 w 1 添加 j 个等于 w 2
dp[i][0]=i
w 1 不空 w 2 是空 w 2 增加 i 个可以与 w 1 相同,或者 w 1 删除 i 个等于 w 2
4. 遍历顺序从前往后
5. 打印 dp
int minDistance(string word1, string word2) {
// 定义dp数组 i-1 j-1 结尾最少操作次数
std::vector<std::vector<int>> dp(word1.size()+1, std::vector<int>(word2.size()+1, 0));
// 初始化dp数组
for (int i = 0; i <= word1.size(); ++i) dp[i][0] = i;
for (int j = 0; j <= word2.size(); ++j) dp[0][j] = j;
// 遍历dp数组
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];
// 由于是word1 变为 word2 只需要 i的操作即可不需要管j
// 不相同 可以删除 dp[i-1][j] 可以插入 dp[i][j-1] 可以替换 dp[i-1][j-1]+1
// 删除 word1 i 等价于 插入 word2 j, 插入 word2 j 等价于删除word1 i
// word1 变为 word2 时 word1 删除就是删除
// word2 变为 word1 时 删除word2 相当于 增加了word1 一个元素
else dp[i][j] = std::min(dp[i-1][j]+1, std::min(dp[i][j-1]+1, dp[i-1][j-1]+1));
}
}
return dp[word1.size()][word2.size()];
}