LeetCode 5:最长回文子串

153 阅读1分钟

最长回文子串

题目描述:

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

示例1

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

示例2

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

示例3

输入: s = "a"
输出: "a"

解题思路 

思路一: 中心扩展算法

从中心开始不断地向两边扩展。如果两边的字母相同,我们就可以继续扩展

枚举所有的「回文中心」并尝试「扩展」,直到无法扩展为止,此时的回文串长度即为此「回文中心」下的最长回文串长度。我们对所有的长度求出最大值,即可得到最终的答案

/**
 * @param {string} s
 * @return {string}
 */
var longestPalindrome = function (s) {
  if (s.length < 2) {
    return s;
  }
  let start = 0,
    maxLength = 1; // 对于ab这种情况
  function expandAroundCenter(left, right) {
    while (left >= 0 && right < s.length && s[left] == s[right]) {
      if (maxLength < right - left + 1) {
        maxLength = right - left + 1;
        start = left;
      }
      left--;
      right++;
    }
  }
  for (var i = 0; i < s.length; i++) {
    expandAroundCenter(i - 1, i + 1);  // 针对cacac这种情况
    expandAroundCenter(i, i + 1);  // 针对babbab
  }
  return s.substring(start, start + maxLength);
};

时间复杂度: O(n2),其中 n 是字符串的长度。

空间复杂度: O(1)

思路二: 动态规划

中心扩散的方法,其实做了很多重复计算。动态规划就是为了减少重复计算的问题(空间换时间,将计算结果暂存起来,避免重复计算)。

我们用一个 布尔型二维数组 dp[l][r] 表示字符串从 i 到 j 这段是否为回文。试想如果 dp[l][r]=true,我们要判断 dp[l-1][r+1] 是否为回文。只需要判断字符串在(l-1)和(r+1)两个位置是否为相同的字符,是不是减少了很多重复计算。

进入正题,动态规划关键是找到初始状态和状态转移方程。

初始状态,l=r 时,此时 dp[l][r]=true。

状态转移方程,dp[l][r]=true 并且(l-1)和(r+1)两个位置为相同的字符,此时 dp[l-1][r+1]=true

var longestPalindrome = function (s) {
  let len = s.length,
    start = 0,
    maxLength = 1;
  if (len < 2) {
    return s;
  }
  let dp = Array(len);
  for (let i = 0; i < len; i++) {
    dp[i] = [];
  }
  for (let r = 1; r < len; r++) {
    for (let l = 0; l < r; l++) {
      if (s[l] == s[r] && (r - l <= 2 || dp[l + 1][r - 1])) {
        dp[l][r] = true;
        if (r - l + 1 > maxLength) {
          maxLength = r - l + 1;
          start = l;
        }
      }
    }
  }
  return s.substring(start, start + maxLength);
}; 

时间复杂度:O(n^2),其中 n 是字符串的长度。动态规划的状态总数为 O(n^2),对于每个状态,我们需要转移的时间为 O(1)。

空间复杂度:O(n^2),即存储动态规划状态需要的空间

参考资料

5. 最长回文子串 - 力扣(LeetCode)