一. 回文子串
- 思路:例如在字符串 “abccfb” 中,我们很容易能够看出有7个子串分别为 “a”, “b”, “c”, “c”, “f”, “b”, “cc”, 7个子串,但要注意子串是连续的,例如他的子串 “bccfb”, 我们不能将其中的 “bccb” 视为答案。要确定 [0,s.length) 区间的字符串是否为回文子串,就要确定其子区间是否为回文子串,即 [ i , j ] i, j ∈ [ 0, s.length )。
此时有三种情况:
①. i === j, 此时为单个字符串必为子串。
②. i === j + 1 && s[ i ] === s[ j ]. 此时两个字符也为回文子串。
③. i > j + 1 && s[ i ] === s[ j ] 此时该区间能否为回文子串取决于该区间的子区间 [ i - 1][ j + 1 ] 是否为回文子串。
④. s[ i ] !== s[ j ] 则区间子串不为回文子串。 - 初始化:在上面的推到中我们确定了地推公式与 j + 1 和 i - 1 有关,则我们的遍历方式要与其相符。dp[0][0]表示第0个字符,自然是回文子串,我们将其初始化为true。
具体代码如下
var countSubstrings = function (s) {
let len = s.length;
//初始化dp数组
let dp = new Array(len).fill(0).map(() => new Array(len).fill(false));
dp[0][0] = true;
//记录回文子串的个数
let res = 1;
for (let i = 1; i < len; i++) {
for (let j = i; j >= 0; j--) {
//遍历顺序根据思路确定
if (s[i] === s[j]) {
if (i <= j + 1) {
dp[i][j] = true;
} else {
dp[i][j] = dp[i - 1][j + 1];
}
} else {
dp[i][j] = false;
}
if (dp[i][j] === true) {
res = res + 1;
}
}
}
return res;
};
二. 最长回文序列
- 思路: 与第一题类似,有区别的地方在与回文序列表示回文子串是可以不连续的,上方的举例 “bccfb” 我们可以将 "bccb"视为一个长度为4的回文子序列。 此时我们要记录的是数据而不只是某区间是否为回文子串了。
- 初始化:dp[0][0]表示第0个字符,自然是回文子串,我们将其初始化为长度1。
具体代码如下
var longestPalindromeSubseq = function (s) {
let len = s.length;
let dp = new Array(len).fill(0).map(() => new Array(len).fill(0));
dp[0][0] = 1;
for (let i = 1; i < len; i++) {
for (let j = i; j >= 0; j--) {
if (s[i] === s[j]) {
if (i <= j + 1) {
//同一个指针为1, 相隔1为2
dp[i][j] = i - j + 1;
} else {
//加上两头的字符,所以加2
dp[i][j] = dp[i - 1][j + 1] + 2;
}
} else {
//此时应该取子区间较大的那一个
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j + 1]);
}
}
}
return dp[len - 1][0];
};
小结
在写此类问题的时候要看清题目要我们做的是序列还是子串,判断其是否连续。注意根据递推公式的符号方向来判断遍历的顺序。