一、方法
回文串是一个正读和反读都一样的字符串(abccba)。一般有两种方法,中心扩展法和动态规划。主要介绍动态规划。
- dp[ i ][ j ] 表示区间范围[ i, j ] 的子串是否是回文子串,如果是,dp[ i ][ j ]为true,否则为false。
- 递推公式
- 初始化:全为false
- 遍历顺序:dp[ i + 1 ][ j - 1] 需要之前 i 之后的,j 之前的。所以 i从后往前遍历,j从前往后遍历
const n = s.length
const dp = new Array(n).fill().map(() => new Array(n).fill(false))
// i从后往前遍历,j从前往后遍历
for (let i=n-1; i>=0; i--) {
for (let j=i; j<n; j++) {
// 下面的情况可以合并
if (s[i] == s[j]) {
if (j - i <= 1) { // 情况一 和 情况二
dp[i][j] = true;
// 其他处理逻辑...
} else if (dp[i + 1][j - 1]) { // 情况三
dp[i][j] = true;
// 其他处理逻辑...
}
}
}
}
二、题目
647. 回文子串
var countSubstrings = function(s) {
// dp[i][j]表示区域范围[i,j]的子串是否是回文子串
const n = s.length
const dp = new Array(n).fill().map(()=>new Array(n).fill(false))
// 回文子串数量
let count = 0
for(let i=n-1; i>=0; i--){
for(let j=i; j<n; j++){
// 首先两边相等,然后判断里面的是否为回文子串
if((s[i] == s[j]) && ((j-i <= 1) || dp[i+1][j-1])){
dp[i][j] = true
count++
}
}
}
return count
};
5. 最长回文子串
var longestPalindrome = function(s) {
// dp[i][j]表示区域范围[i,j]的子串是否是回文子串,最后拿到的是子串,需要区间
const n = s.length
const dp = new Array(n).fill().map(()=>new Array(n).fill(false))
// 拿到最长回文子串的区间和长度,因为最后是需要返回具体的子串
let left = 0 ,len = 0;
for(let i=n-1; i>=0; i--){
for(let j=i; j<n; j++){
// 首先两边相等,然后判断里面的是否为回文子串
if((s[i] == s[j]) && ((j-i <= 1) || dp[i+1][j-1])){
dp[i][j] = true
// 这个回文子串的长度是j-i+1
if(len < j-i+1){
len = j-i+1
left = i
}
}
}
}
return s.slice(left,left+len)
};
516. 最长回文子序列
var longestPalindromeSubseq = function(s) {
const n = s.length
// dp[i][j]表示i~j范围内的最长回文子序列的长度
const dp = new Array(n).fill().map(() => new Array(n).fill(0))
// 初始化
// i从后往前遍历,j从前往后遍历
for(let i=n-1; i>=0; i--){
dp[i][i] = 1
for(let j=i+1; j<n; j++){
if(s[i] == s[j]){
// 由于是子序列,所以头尾相等后,只需要将上一个状态的加上前后即可
// 不需要去考虑里面的是否是回文子串
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[0][n-1]
};