给定一个字符串,找到其中最长的回文子串
回文串的定义为左右对称,例如 abcba ,这个字符串以 c 为中心左右对称;又例如 abccba ,这个字符串也是对称的,虽然中心不在某个字符上。
现在假设字符串 str 为 abcdeffeab ,不难看出其中有一个回文子串 abcd effe ab,有假设现在字符串为 abadeffea,里面有两个回文子串 aba d effe a,那么这个字符串的最长回文子串应该为 effe
由于字符串 str 中可能出现多个回文子串,因此,现在假定有两个索引 left 和 right,表示字符串 str 中索引 left ~ right 的字符子串,现在我们需要判断这个字符子串是否为回文串,只有当str[ left ] = str[ right ]才有可能是回文串,此时有以下几种情况:
- left = right,此时这个子串只有一个字符,当然为回文串
- left + 1 = right,此时子串有两个字符,且字符相同,比如aa,bb等,这时也是回文串
- left + 1 < right,此时字符串有多个字符,因此,这个 left ~ right 子串为回文串当且仅当 left + 1 ~ right - 1 为回文串,假设目前 left ~ right 子串为 abcba,因为 bcb 为回文串,所以 abcba 为回文串;又假设 left ~ right 子串为 abcda,因为 bcd 不为回文串,所以 abcda不为回文串。
因此,使用二维动态规划 dp[i][j] 实现,dp[i][j] = true 字符串从索引 i ~ j的子串为回文串,false表示不是回文串,因此,状态转移方程为:dp[i][j] =
- false (str[i] != str[j])
- (str[i] = str[j])
- true (i = j 或者 i + 1 = j):情况1
- true (dp[i + 1][j - 1] = true):情况2
- false (其他情况)
代码如下
let longestPalindrome = function(str) {
// 首先初始化dp数组,由于i一定 <= j,因此,将二维数组右上角一半初始化为false
let dp = new Array(s.length)
for(let i = 0; i < s.length; i++) {
dp[i] = new Array(s.length)
}
for(let i = 0; i < s.length; i++) {
for(let j = i; j < s.length; j++) {
dp[i][j] = false
}
}
let maxLen = 0 // 记录当前最长回文子串的长度
let left = 0 // 当前最长回文子串的左索引
let right = 0 // 当前最长回文子串的右索引
// 注意:由于dp[i][j]的值与dp[i + 1][j - 1]的值有关,即此坐标的右上角,因此遍历应为右上往左下
for(let i = str.length - 1; i >= 0; i--) {
for(let j = i; j < str.length; j++) {
if(s[i] === s[j]) { // 首先得满足str[i] = str[j]
if(j - i <= 1) { // 情况1
dp[i][j] = true
}
else if(dp[i + 1][j - 1]) { // 情况2
dp[i][j] = true
}
}
if(dp[i][j] && j - i + 1 > maxLen) { // 记录当前最长回文子串的长度以及左右索引
maxLen = j - i + 1
left = i
right = j
}
}
}
return s.substring(left, right + 1)
};
此时时间复杂度为O(n2),空间复杂度为O(n2)
优化可以考虑使用双指针法可以将空间复杂度降到O(1),后续…