【中等】算法:最长回文子串

627 阅读2分钟

最长回文子串

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第5天,点击查看活动详情

最长回文子串

算题:

给你一个字符串 s,找到 s 中最长的回文子串。

示例 1:

输入: s = "babad"
输出: "bab"
解释: "aba" 同样是符合题意的答案。

示例 2:

输入: s = "cbbd"
输出: "bb"

提示:

  • 1 <= s.length <= 1000
  • s 仅由数字和英文字母组成

解析

回文字符串在算法中为常见字符串,要求正反颠倒后还是相等的字符串。本题不仅要找出给定字符串中的存在的回文字符串,并且要返回最长的一个。假定我们使用暴力破解可以想象是如何求得:

  1. 遍历字符串找出所有可能出现的子字符串
  2. 分别对子字符串判断是否为回文
  3. 找出最大的回文字符串

这样的方法理论可行,但在实际操作中肯定会发现第一步找出所有可能出现的子字符串在复杂度上就已经远远超出要求了。大概率会超时,再加上第二步判断回文的算法,又大大减少代码的效率,所以不可取。

所以正确的处理方法是将第一步优化,并且结合第二步去判断回文,最终找出最长回文子字符串。

双指针法图解

1.gif

图解思路:

  1. 遍历数组,每一个循环的当前元素为每次重置L指针的位置。
  2. 每一个循环中比较 L 和 R 指针对应的字符是否一样。
  3. 如果一样那么表示他们可以组成回文,记录下当前的回文作为当前最大回文。继续扩大左右指针,寻找是否可以组成更大的回文。
  4. 如果不一样那么开始下一个循环。

算法代码:

/**
 * @param {string} s
 * @return {string}
 */
var longestPalindrome = function(s) {
    let start = 0
    let max = 1
    for (let index = 0 ; index < s.length; index++) {
        let left = index - 1
        let right = index
        while(left >=0 && right < s.length && s[left] === s[right]) {
            left--
            right++
        }
        if (right - left - 1 > max) {
            max = right - left - 1
            start = left + 1
        }
        
    }
    return s.substring(start, start + max)

};

奇数回文

假设字符串 s= 'abcba,答案应该是 abcba, 但是当遍历到c的时候,left 和 right 指针并不能在 c 上组成回文。原因是最大回文的字符串长度为奇数,这就表示 left 指针在最中间的时候并不能和 right 指针指向的字符串相等,所以会被判定不是回文。

简单的说: 最大回文中间的那个字符是不能通过匹配相等来判定的。所以该算法不适用完整的测试用例。

解决奇数回文的情况

不难发现,如果我们只考虑奇数回文,那么我们只要将当前循环遍历元素的下标 -1 作为 left, +1 作为 right 重置位置即可。

1.gif

算法原理和偶数回文基本一致。

奇数回文代码

/**
 * @param {string} s
 * @return {string}
 */
var longestPalindrome = function(s) {
    let start = 0
    let max = 1
    for (let index = 0 ; index < s.length; index++) {
        let left = index - 1
        let right = index + 1
        while(left >=0 && right < s.length && s[left] === s[right]) {
            left--
            right++
        }
        if (right - left - 1 > max) {
            max = right - left - 1
            start = left + 1
        }
        
    }
    return s.substring(start, start + max)

};

奇偶合并

在循环中分别实现奇数偶数回文处理:

var longestPalindrome = function (s) {
  let len = s.length; // 字符串长度
  if (len < 2) return s; // 如果字符串长度小于2,直接返回字符串
  let max = 1; // 最长回文子串的长度
  let start = 0; // 最长回文子串的起始位置
  for (let i = 0; i < len; i++) { // 循环遍历字符串
    // 奇数回文解决
    let left = i - 1; // 左指针
    let right = i + 1; // 右指针
    while (left >= 0 && right < len && s[left] === s[right]) { // 左右指针同时向两边移动,直到左右指针相遇或者越界
      left--; // 左指针向左移动
      right++; // 右指针向右移动
    }
    if (right - left - 1 > max) { // 如果当前回文子串的长度大于最长回文子串的长度
      max = right - left - 1; // 更新最长回文子串的长度
      start = left + 1; // 更新最长回文子串的起始位置
    }

    // 偶数回文解决
    left = i; // 左指针重置为当前位置
    right = i + 1; // 右指针重置为当前位置
    while (left >= 0 && right < len && s[left] === s[right]) { // 左右指针同时向两边移动,直到左右指针相遇或者越界
      left--; // 左指针向左移动
      right++; // 右指针向右移动
    }
    if (right - left - 1 > max) { // 如果当前回文子串的长度大于最长回文子串的长度
      max = right - left - 1; // 更新最长回文子串的长度
      start = left + 1; // 更新最长回文子串的起始位置
    }
  }
  return s.substring(start, start + max); // 返回最长回文子串
};

最后附上提交结果:

image.png