115. 不同的子序列
dp[i][j]:考虑 i - 1 的 s 包含的考虑 j - 1 的 t 的个数。
递推公式:
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]
dp[i][j] = dp[i - 1][j]
s 遍历到了 babga,t 是 bag
假设 s 的下一个字符是 g
那么可能是 babgag 里找 bag,也可能是 babga 里找 ba
初始化:第一列全为 1
class Solution {
public int numDistinct(String s, String t) {
int n1 = s.length();
int n2 = t.length();
int[][] dp = new int[n1 + 1][n2 + 1]; // dp[i][j]:考虑 i - 1 的 s 包含考虑 j - 1 的t 的个数。
for (int i = 0; i <= n1; i++) { // 初始化:第一列全为 1
dp[i][0] = 1;
}
for (int i = 1; i <= n1; i++) {
for (int j = 1; j <= n2; j++) {
if (s.charAt(i - 1) == t.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
} else {
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[n1][n2];
}
}
583. 两个字符串的删除操作
方法一:正常考虑
dp[i][j]:以 i - 1、j - 1 结尾的两个字符串要相同的最小操作次数。
递推公式:
dp[i][j] = dp[i - 1][j - 1]
dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1)
初始化:第一行和第一列都初始化为对应考虑的字符串长度。
class Solution {
public int minDistance(String word1, String word2) {
int n1 = word1.length();
int n2 = word2.length();
int[][] dp = new int[n1 + 1][n2 + 1]; // dp[i][j]:以 i - 1、j - 1 结尾的两个字符串要相同的最小操作次数。
for (int i = 0; i <= n1; i++) { // 初始化:删字符串个长度才能变成空串
dp[i][0] = i;
}
for (int j = 0; j <= n2; j++) {
dp[0][j] = j;
}
for (int i = 1; i <= n1; i++) {
for (int j = 1; j <= n2; j++) {
if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1);
}
}
}
return dp[n1][n2];
}
}
方法二:转换成最长公共子序列
class Solution {
public int minDistance(String word1, String word2) {
int n1 = word1.length();
int n2 = word2.length();
int[][] dp = new int[n1 + 1][n2 + 1];
for (int i = 1; i <= n1; i++) {
for (int j = 1; j <= n2; j++) {
if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return n1 + n2 - 2 * dp[n1][n2];
}
}
72. 编辑距离
dp[i][j]:以 i - 1、j - 1 结尾的最小编辑次数为 dp[i][j]。
递推公式:
dp[i][j] = dp[i - 1][j - 1]
dp[i][j] = Math.min(Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1), dp[i - 1][j - 1] + 1)
(前两个为删除,后一个为替换。(新增和删除等价。))
初始化:初始化为考虑的字符串长度。
class Solution {
public int minDistance(String word1, String word2) {
int n1 = word1.length();
int n2 = word2.length();
int[][] dp = new int[n1 + 1][n2 + 1]; // dp[i][j]:以 i - 1、j - 1 结尾的最小编辑次数为 dp[i][j]。
for (int i = 0; i <= n1; i++) { // 初始化:初始化为考虑的字符串长度。
dp[i][0] = i;
}
for (int j = 0; j <= n2; j++) {
dp[0][j] = j;
}
for (int i = 1; i <= n1; i++) {
for (int j = 1; j <= n2; j++) {
if (word1.charAt(i - 1) == word2.charAt(j -1)) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = Math.min(Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1), dp[i - 1][j - 1] + 1); // 前两个为删除,后一个为替换。(新增和删除等价。)
}
}
}
return dp[n1][n2];
}
}
优化:对特殊情况的巧妙判断
// 有一个字符串为空串
if (n1 * n2 == 0) {
return n1 + n2;
}
子序列问题总结
- 评论区笔记
- 评论区笔记2
一般如果要是求的子数组是连续的,或者即使不是连续的,要求单调这种,都是需要定义 dp 是以 nums[i] 为结尾,最终结果是 max(dp)。因为如果不定义是以 nums[i] 为结尾,那么这时候即使做 nums[i] 的比较,也无法更新 dp[i],因为不知道 dp[i-1] 是不是以 nums[i-1] 结尾,如果不是,题目要求的数组的连续的,那就没法 dp[i] = dp[i-1]+1。又或者类似递增子序列,我们需要 dp[i-1] 的最后一位的具体取值是什么,才好和当前的 nums[i] 做判断。
关键点在于,这道题我们需不需要记录住 dp[i] 对应的状态的最后一个取值,例如最长递增子序列,最长重复子数组,我们是需要记住的。此时需要定义 dp[i] 为以 nums[i] 为结尾的。而像最长公共子序列等,我们不需要知道 dp[i-1] 对应的最长公共子序列的最后一位具体的 index 或者 value,这个时候就不需要要求 dp[i] 是以 nums[i] 为结尾的,只要定义在 [0, i] 这个区间内就可以。
388

被折叠的 条评论
为什么被折叠?



