异名解题:5. 最长回文子串

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

测试用例一 输入: "babad" 输出: "bab" ("aba" 也是一个有效答案)
测试用例二 输入: "cbbd" 输出: "bb"

解题思路

方法一O(n³): 暴力遍历

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;总结一下问题主要是由于递归解法的效率比较低,函数重复嵌套调用,而且并没有提炼出相同的子问题

方法二O(n²):动态规划/中心扩展

方法一执行超时之后换种指导思想,往动态规划方向想,想到的第一个状态转移方程是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#,回文中心变成了#也是一个字母,在这个基础上使用中心扩展就不用考虑两种情况了,这其实也是马拉车算法的第一步。

????文章整理在GitHub,点击查看原文跳转

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值