115.不同的子序列
题目链接:115.不同的子序列
文档讲解:代码随想录
状态:不会
思路:
dp[i][j] 表示在 s 的前 j 个字符中,t 的前 i 个字符作为子序列出现的次数。
匹配的情况:
1.当 s[j-1] 与 t[i-1] 匹配时,说明我们可以用 s[j-1] 来匹配 t[i-1],这种情况下,在 s 的前 j-1 个字符中找到的所有 t 的前 i-1 个字符作为子序列的组合数(dp[i-1][j-1])都可以作为当前组合的一部分。例如: t:ba(g) s:bag(g) ,当t[2]=s[3]时,如果使用s[3]的话,dp[2][3]可以由dp[1][2]得到,也就是t:ba(g)在s:bag(g)中的出现次数可以由ba在bag出现的次数推导过来,因为t和s都加上一个相同的g。这是推导dp[i][j]的可能情况之一。
2.此外,即使不使用 s[j-1] 也可以完成匹配,这种情况下,只需要在 s 的前 j-1 个字符中找到 t 的前 i 个字符作为子序列的组合数(dp[i][j-1])。例如:t:ba(g) s:bag(g) ,如果不使用s[3],就是看 t:ba(g) 和 s:ba(g) ,这种情况 dp[2][3] 可以由dp[2][2]得到,也就是t:ba(g)在s:bag(g)中的出现次数可以由t:bag在s:bag出现的次数推导过来。这也是推导dp[i][j]的一种情况。
所以: s[j-1] 与 t[i-1] 匹配时,dp[i][j] = dp[i - 1][j - 1] + dp[i][j - 1];
不匹配的情况:
当 s[j-1] 与 t[i-1] 不匹配时,说明我们不能用 s[j-1] 来匹配 t[i-1]。这种情况下,我们只能在 s 的前 j-1 个字符中找到 t 的前 i 个字符作为子序列的组合数(dp[i][j-1])。
初始状态
空字符串 t 是任何字符串 s 的子序列:因此,对于任何 j,dp[0][j] = 1。
空字符串 s 不能包含非空字符串 t:因此,对于任何 i > 0,dp[i][0] = 0。
题解:
public int numDistinct(String s, String t) {
// 将字符串转换为字符数组
char[] sChars = s.toCharArray();
char[] tChars = t.toCharArray();
// 创建DP数组,dp[i][j]表示t的前i个字符在s的前j个字符中出现的次数
int[][] dp = new int[t.length() + 1][s.length() + 1];
// 初始化 dp[0][j] 为 1,因为空字符串t是任何字符串s的子序列
for (int j = 0; j <= s.length(); j++) {
dp[0][j] = 1;
}
// 填充DP数组
for (int i = 1; i <= t.length(); i++) {
for (int j = 1; j <= s.length(); j++) {
if (tChars[i - 1] == sChars[j - 1]) {
// 如果当前字符匹配,可以选择匹配和不匹配两种情况
dp[i][j] = dp[i - 1][j - 1] + dp[i][j - 1];
} else {
// 如果当前字符不匹配,只能不匹配
dp[i][j] = dp[i][j - 1];
}
}
}
// 返回t的前t.length()个字符在s的前s.length()个字符中出现的次数
return dp[t.length()][s.length()];
}
583. 两个字符串的删除操作
题目链接:583. 两个字符串的删除操作
文档讲解:代码随想录
状态:做出来了有点磕绊
思路1:找到最大公共子序列数量,然后两个字符串分别减去它求和。
思路2:
dp[i][j]表示word1[0,i],word2[0,j],要使它们相同一共需要删去多少个字符。
当word1[i - 1] 与 word2[j - 1]相同的时候,显然dp[i][j] = dp[i - 1][j - 1];
当word1[i - 1] 与 word2[j - 1]不相同的时候:
情况一:删word1[i - 1],最少操作次数为dp[i - 1][j] + 1
情况二:删word2[j - 1],最少操作次数为dp[i][j - 1] + 1
情况三:同时删word1[i - 1]和word2[j - 1],操作的最少次数为dp[i - 1][j - 1] + 2
其中情况三隐含在情况一和二中,所以应该是情况1和2中应该取最小操作数,所以dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1);
初始化:
dp[i][0]:word2为空字符串,以i-1为结尾的字符串word1要删除多少个元素,才能和word2相同呢,很明显dp[i][0] = i。
dp[0][j]的话同理
题解:
public int minDistance(String word1, String word2) {
// 将字符串转换为字符数组
char[] w1 = word1.toCharArray();
char[] w2 = word2.toCharArray();
// 创建DP数组,dp[i][j]表示word1的前i个字符和word2的前j个字符的最长公共子序列长度
int[][] dp = new int[word1.length() + 1][word2.length() + 1];
// 填充DP数组
for (int i = 1; i <= w1.length; i++) {
for (int j = 1; j <= w2.length; j++) {
if (w1[i - 1] == w2[j - 1]) {
// 如果当前字符匹配,LCS长度加1
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
// 如果当前字符不匹配,选择两个子问题中LCS最长的一个
dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]);
}
}
}
// 计算最少步骤:将两个字符串转换成它们的LCS需要的删除和插入操作数
return word1.length() + word2.length() - 2 * dp[word1.length()][word2.length()];
}
public int minDistance2(String word1, String word2) {
char[] w1 = word1.toCharArray();
char[] w2 = word2.toCharArray();
int m = word1.length(), n = word2.length();
// 创建DP数组,dp[i][j]表示word1的前i个字符与word2的前j个字符变得相同所需删除的字符数
int[][] dp = new int[m + 1][n + 1];
// 初始化边界情况:将word1的前i个字符变为空字符串需要i步操作(全删除)
for (int i = 1; i <= m; i++) {
dp[i][0] = i;
}
// 初始化边界情况:将空字符串变成word2的前j个字符需要j步操作(全删除)
for (int j = 1; j <= n; j++) {
dp[0][j] = j;
}
// 填充DP数组
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (w1[i - 1] == w2[j - 1]) {
// 当前字符相等,不需要额外删除
dp[i][j] = dp[i - 1][j - 1];
} else {
// 当前字符不相等,选择删除word1[i - 1]或word2[j - 1]中的最小删除次数
dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1);
}
}
}
// 返回最终结果
return dp[m][n];
}
//最少编辑距离思路
public int minDistance3(String word1, String word2) {
// 将字符串转换为字符数组
char[] w1 = word1.toCharArray();
char[] w2 = word2.toCharArray();
int m = word1.length(), n = word2.length();
// 创建DP数组,dp[i][j]表示将word1的前i个字符转换为word2的前j个字符所需的最少操作数
int[][] dp = new int[m + 1][n + 1];
// 初始化边界情况:word1的前i个字符转换为空字符串需要i步操作(全删除)
for (int i = 1; i <= m; i++) {
dp[i][0] = i;
}
// 初始化边界情况:空字符串转换为word2的前j个字符需要j步操作(全插入)
for (int i = 1; i <= n; i++) {
dp[0][i] = i;
}
// 填充DP数组
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (w1[i - 1] == w2[j - 1]) {
// 如果当前字符匹配,不需要额外操作
dp[i][j] = dp[i - 1][j - 1];
} else {
// 如果当前字符不匹配,选择三种操作中的最小值(插入、删除、替换)
dp[i][j] = Math.min(dp[i - 1][j] + 1, // 删除操作
Math.min(dp[i][j - 1] + 1, // 插入操作
dp[i - 1][j - 1] + 1)); // 替换操作
}
}
}
// 返回将word1转换为word2所需的最少操作数
return dp[m][n];
}
72. 编辑距离
题目链接:72. 编辑距离
文档讲解:代码随想录
状态:不会
思路:
dp[i][j] 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]。
显然if (word1[i - 1] == word2[j - 1]) 那么说明不用任何编辑,dp[i][j] 就应该是 dp[i - 1][j - 1],即dp[i][j] = dp[i - 1][j - 1];
if (word1[i - 1] != word2[j - 1]),此时就需要编辑了,如何编辑呢?
操作一:word1删除一个元素,那么就是word1[0,i-1] 与 word2[0,j] 的最近编辑距离 再加上一个操作。
即 dp[i][j] = dp[i - 1][j] + 1;
操作二:word2插入一个元素,那么就是word1[0,i] 与 word2[0,j-1] 的最近编辑距离 再加上一个操作。
操作三:替换元素,word1替换word1[i - 1],使其与word2[j - 1]相同,只需要一次替换的操作。
所以递归公式如下:
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;
}
初始化:
word1的前i个字符转换为空字符串需要i步操作(全删除)
空字符串转换为word2的前j个字符需要j步操作(全插入)
题解:
public int minDistance(String word1, String word2) {
// 将字符串转换为字符数组
char[] w1 = word1.toCharArray();
char[] w2 = word2.toCharArray();
int m = word1.length(), n = word2.length();
// 创建DP数组,dp[i][j]表示将word1的前i个字符转换为word2的前j个字符所需的最少操作数
int[][] dp = new int[m + 1][n + 1];
// 初始化边界情况:word1的前i个字符转换为空字符串需要i步操作(全删除)
for (int i = 1; i <= m; i++) {
dp[i][0] = i;
}
// 初始化边界情况:空字符串转换为word2的前j个字符需要j步操作(全插入)
for (int i = 1; i <= n; i++) {
dp[0][i] = i;
}
// 填充DP数组
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (w1[i - 1] == w2[j - 1]) {
// 如果当前字符匹配,不需要额外操作
dp[i][j] = dp[i - 1][j - 1];
} else {
// 如果当前字符不匹配,选择三种操作中的最小值(插入、删除、替换)
dp[i][j] = Math.min(dp[i - 1][j] + 1, // 删除操作
Math.min(dp[i][j - 1] + 1, // 插入操作
dp[i - 1][j - 1] + 1)); // 替换操作
}
}
}
// 返回将word1转换为word2所需的最少操作数
return dp[m][n];
}