一、判断子序列
字符串的子序列是原始字符串删除一些(或不删除)字符,不改变剩余字符相对位置的新字符串
五部曲
- 确定dp数组的含义,
dp[i][j],代表下标i-1j结尾的字符串s,和下标j-1结尾的字符串t,相同子序列的长度 - 确定递推公式
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[i-1]和t[j-2]的比较结果
}
- dp数组初始化,
dp[0][0]和dp[i][0]都初始化为0 - 遍历顺序为从上到下,从左到右
- 举例推导
时间复杂度:O(n × m) 空间复杂度:O(n × m)
/**
* @param {string} s
* @param {string} t
* @return {boolean}
*/
var isSubsequence = function(s, t) {
let dp = new Array(s.length + 1).fill(0).map(_ => new Array(t.length + 1).fill(0))
for(let i = 1; i <= s.length;i++) {
for(let j= 1;j <= t.length;j++) {
if(s[i-1] === t[j-1]) {
dp[i][j] = dp[i-1][j-1] + 1
} else {
dp[i][j] = dp[i][j-1]
}
}
}
return dp[s.length][t.length] === s.length
};
二、不同的子序列
字符串s和t,在s的子序列中t出现的个数
五部曲
- 确定dp数组的含义,
dp[i][j],以i-1结尾的s子序列中出现以j-1结尾的t的个数 - 递推公式
if(s[i-1] === t[j-1]) {
// 用s[i-1],或者不用,两种情况都可能匹配
dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
} else {
dp[i][j] = dp[i-1][j]
}
- dp数组的初始化,
dp[i][j]是从上方和左上方推导而来,dp[i][0]和dp[0][j]需要初始化
dp[i][0] = 1 // 以i-1结尾的字符串,出现空字符串的个数
dp[0][j] = 0
dp[0][0] = 1 // 空字符串,出现空字符串的个数为1
- 遍历顺序,左上方和正上方
- 举例推导dp
/**
* @param {string} s
* @param {string} t
* @return {number}
*/
var numDistinct = function(s, t) {
let dp = new Array(s.length + 1).fill(0).map(_ => new Array(t.length + 1).fill(0))
for(let i = 0; i <= s.length; i++) {
dp[i][0] = 1
}
for(let i = 1; i <= s.length;i++) {
for(let j = 1; j <= t.length; j++) {
if(s[i-1] === t[j-1]) {
dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
} else {
dp[i][j] = dp[i-1][j]
}
}
}
return dp[s.length][t.length]
};