给定一个字符串 s
,找到 s
中最长的回文子串。你可以假设 s
的最大长度为 1000。
测试用例一 输入: "babad" 输出: "bab" ("aba" 也是一个有效答案) 测试用例二 输入: "cbbd" 输出: "bb"
第N
个符合条件的字符串,等于前N-1
个字符串加上第N
个字符然后取反,那么这个字符串就是回文字符串。它只能找出从头开始的字符,所以还得在外层添加一个循环,更改字符串的起点。以下就是该解法写出来的函数,递归部分复杂度是O(n²),然后外层再套一个N层的遍历,所以它的复杂度是O(n³)。
function longestPalindrome(str) {
let idx = 0;
let startIdx = 0;
let len = str.length;
let maxSubStr = str[0];
let curStr = '';
if (len <= 1) return str;
while(startIdx < len - 1) {
curStr = str[startIdx]
idx = startIdx
function loop() {
while (idx < len - 1) {
idx++;
curStr = curStr + str[idx]
if (curStr.split('').reverse().join('') == curStr && curStr.length >= maxSubStr.length) {
maxSubStr = curStr;
}
loop()
}
}
loop()
startIdx+=1;
}
return maxSubStr
}
但是在LeetCode上提交的时候,第41个用例超时了。该用例的长度为877,我本地在不限时间地跑该用例的耗时是3569.156ms
,最长回文子串为fklkf
;总结一下问题主要是由于递归解法的效率比较低,函数重复嵌套调用,而且并没有提炼出相同的子问题
方法一执行超时之后换种指导思想,往动态规划方向想,想到的第一个状态转移方程是F(n+1) == str[n-1]+F(n)+str[n+1] 且满足 str[n+1] == str[n-1]
。它刚好可以用递推来实现,因为每个单独的字母都是一个符合条件的答案,然后往左右递增扩展,如果左右相同,那就能够得出下一个回文,直到找到最长的回文。但是这样解的话还要区分两种情况,如果回文的长度是基数,比如cabad
的输出是aba
,它的中心位是一个字母,但是如果回文的长度是偶数,比如abbc
的输出是bb
,那它的中心位就是两个字母,所以使用这个方法得分别对这两种情况做处理,下面的代码就是基于这种解法而来,官方解法更简洁
function longestPalindrome(str) {
if (str.length < 2) return str;
let maxStr = str[0];
for (let curIdx = 0; curIdx < str.length - 1; curIdx++) {
let str1 = search(str, curIdx, true);
let str2 = search(str, curIdx, false);
let result = str1.length > str2.length ? str1 : str2;
maxStr = result.length > maxStr.length ? result : maxStr;
}
return maxStr;
}
function search(str, curIdx, isEven) {
let prevIdx, nextIdx;
let result = str[curIdx];
if (isEven) {
if (str[curIdx] == str[curIdx + 1]) {
result += str[curIdx + 1];
} else {
return result;
}
}
let maxLoopTime = Math.min(str.length - curIdx, curIdx);
for (let n = 1; n <= maxLoopTime; n++) {
prevIdx = curIdx - n;
nextIdx = isEven ? curIdx + n + 1 : curIdx + n;
if (str[prevIdx] != str[nextIdx] || str[prevIdx] == undefined) break;
result = str[prevIdx] + result + str[nextIdx];
}
return result;
}
方法二可以改良,上面的逻辑针对回文中心是一个字母的和两个字母两种情况分别做了处理,如果取巧一点,往字符串前后,以及每个字母之间插入一个#
,就能够把回文中心是两个字母的情况给去掉,比如cabad
插入后变成#c#a#b#a#d#
,它的输出是#a#b#a#
,回文中心还是字母;abbc
插入后变成#a#b#b#c#
,它的输出是#b#b#
,回文中心变成了#
也是一个字母,在这个基础上使用中心扩展就不用考虑两种情况了,这其实也是马拉车算法的第一步。