链接: 392.判断子序列
本题与最大子序和很像,只不过最大子序和是求公共的字符串,而本题其中一个字符串可能将另一个字符串包含在内。
但按最大子序和的思路也是可以的
递推公式不相等的时候,因为其中一个字符串比较短,一定被包含在内,所以我们不需要再从当前元素的上方元素和左方元素中取最大,而是最长的字符串它的下标左移一位。
遍历顺序:要根据递推公式来考虑遍历顺序
if(sChar[i - 1] == tChar[j - 1]){
dp[i][j] = dp[i-1][j-1] + 1;
}else{
dp[i][j] = dp[i][j-1];
}
元素都是由左上方 和 左方的元素推出来,所以从上至下,从左至右
具体s和t在内在外遍历时都可以
但是一定要注意统一
PS:还是按五部曲写一下子
不然自己写着写着晕了
// s遍历在外,t遍历在内
class Solution {
public boolean isSubsequence(String s, String t) {
char[] sChar = s.toCharArray();
char[] tChar = t.toCharArray();
int[][] dp = new int[sChar.length + 1][tChar.length + 1];
for(int i = 1; i <= sChar.length; i++){
for(int j = 1; j <= tChar.length; j++){
if(sChar[i - 1] == tChar[j - 1]){
dp[i][j] = dp[i-1][j-1] + 1;
}else{
dp[i][j] = dp[i][j-1];
}
}
}
return dp[sChar.length][tChar.length] == sChar.length;
}
}
// t遍历在外,s遍历在内
// 不止是遍历顺序,二维数组定义也要颠倒一下
// 还是按步骤来,不要想当然。起码在二维数组定义时要写清二维数组的含义。
class Solution {
public boolean isSubsequence(String s, String t) {
char[] sChar = s.toCharArray();
char[] tChar = t.toCharArray();
int[][] dp = new int[tChar.length + 1][sChar.length + 1];
for(int i = 1; i <= tChar.length; i++){
for(int j = 1; j <= sChar.length; j++){
if(tChar[i - 1] == sChar[j - 1]){
dp[i][j] = dp[i-1][j-1] + 1;
}else{
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[tChar.length][sChar.length] == sChar.length;
}
}
链接: 115.不同的子序列
本题条件如果要求t子序列在s子序列是连续出现的,那么该问题就是KMP问题【最近复习到了KMP问题,但不太记得了,需要注意】
本题纯靠想的想不出来,需要在草稿纸上记录一下
按常规的动态规划五部曲
1.dp数组及下表的含义
二维数组:以i - 1结尾的s子序列中出现以j - 1为结尾的t的个数
2.确定递推公式
分为两种情况
s[i - 1]和t[j - 1]相等时
s[i - 1]和t[j - 1]不相等时
首先明确本题设置的二维数组求的是出现的个数,而不是出现序列的长度
当相等时
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
等号右侧前一部分很好理解,就是按照我们一般理解,当相等时,我们肯定看s字符和t字符的前一个字符的匹配情况
本题中因为s中可能出现重复的字母,所以我们需要左移s序列i - 2来与t中的j - 1进行比较;两者相加。
3.初始化
不要想当然,本题二维数组含义与判断子序列不同,因此初始化也不一样
t 去匹配 s
dp[i] [0] = 1; 一个空的字符串匹配有元素的字符串,结果为1
dp[0] [j] = 0;
dp[0] [0] = 1;
4.遍历顺序
由递推公式可以看出,二维数组都是由左上方和正上方推出来的
所以遍历的时候一定从上到下,从左到右。
5.举例推导:暂略
class Solution {
public int numDistinct(String s, String t) {
char[] sChar = s.toCharArray();
char[] tChar = t.toCharArray();
int[][] dp = new int[sChar.length + 1][tChar.length + 1];
for (int i = 0;i < sChar.length + 1;i++){
dp[i][0] = 1;
}
for (int i = 1;i < sChar.length + 1;i++){
for (int j = 1;j <tChar.length + 1;j++){
if (sChar[i - 1] == tChar[j - 1]){
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
}else{
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[sChar.length][tChar.length];
}
}
链接: 583. 两个字符串的删除操作
本题是求两个字符串相同所需的最小步数
有两种方法,一种是求最长公共子序列,然后用word1和word2长度相加-2倍的最长公共子序列长度。
第二种方法,就是动态规划的一般步骤
1.含义:下标为i - 1的word1和下标为j - 1的word2相同所需的最小步数
2.递推公式
如果相等,则等于该元素左上角的元素值
如果不等,分为三种情况
下标i - 1的word1元素可能和j - 2的word2元素相等 + 1
i - 2 的元素可能和 j - 1的元素相等 + 1
i - 2 的元素可能和 j - 2 的元素(需要删除两个,+ 2
此种情况可以倍前两种情况概括
class Solution {
public int minDistance(String word1, String word2) {
// dp[i][j]含义:下标为i - 1的word1和下标为j - 1的word2相同所需的最小步数
int[][] dp = new int[word1.length() + 1][word2.length() + 1];
for (int i = 0;i <= word1.length();i++){
dp[i][0] = i;
}
for (int j = 0;j <= word2.length();j++){
dp[0][j] = j;
}
for (int i = 1;i <= word1.length();i++){
for (int j = 1;j <= word2.length();j++){
if (word1.charAt(i - 1) == word2.charAt(j - 1)){
dp[i][j] = dp[i - 1][j - 1];
}else{
// + 1逻辑上应该写在里面,之前漏掉后犯懒了
dp[i][j] = Math.min(dp[i][j - 1] ,dp[i - 1][j]) + 1;
}
}
}
return dp[word1.length()][word2.length()];
}
}
链接: 72. 编辑距离
本题思路比两个字符串的删除操作要复杂一点
同样可以对两个字符串进行操作,但是两个字符的删除操作只能进行删除操作,而编辑距离可以对字符串进行插入、删除、替换操作
递推公式有变化,其余和两个字符的删除操作类似。
在整个动规的过程中,最为关键就是正确理解dp[i][j]
的定义!
if (word1[i - 1] != word2[j - 1])
即 dp[i][j] = dp[i - 1][j] + 1;
即 dp[i][j] = dp[i][j - 1] + 1;
word2添加一个元素,相当于word1删除一个元素,例如 word1 = "ad" ,word2 = "a"
,word1
删除元素'd'
和 word2
添加一个元素'd'
,变成word1="a", word2="ad"
, 最终的操作数是一样!
操作三:替换元素,word1
替换word1[i - 1]
,使其与word2[j - 1]
相同,此时不用增删加元素。
可以回顾一下,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] + 1;
综上,当 if (word1[i - 1] != word2[j - 1])
时取最小的,即:dp[i][j] = min({dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]}) + 1;
class Solution {
public int minDistance(String word1, String word2) {
// dp[i][j]含义:下标为i - 1的word1和下标为j - 1的word2相同所需的最小步数
int[][] dp = new int[word1.length() + 1][word2.length() + 1];
for (int i = 0;i <= word1.length();i++){
dp[i][0] = i;
}
for (int j = 0;j <= word2.length();j++){
dp[0][j] = j;
}
for (int i = 1;i <= word1.length();i++){
for (int j = 1;j <= word2.length();j++){
if (word1.charAt(i - 1) == word2.charAt(j - 1)){
dp[i][j] = dp[i - 1][j - 1];
}else{
// + 1逻辑上应该写在里面,之前漏掉后犯懒了
dp[i][j] = Math.min(Math.min(dp[i][j - 1] ,dp[i - 1][j]),dp[i - 1][j - 1]) + 1;
}
}
}
return dp[word1.length()][word2.length()];
}
}