题目

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

示例1

  1. 输入: "babad"
  2. 输出: "bab"
  3. 注意: "aba" 也是一个有效答案。

实现

思路1 暴力匹配

  • 枚举所有长度大于等于 22 的子串,依次判断它们是否是回文
  • 具体实现时,可以只针对大于“当前得到的最长回文子串长度”的子串进行“回文验证”
  • 在记录最长回文子串时,只记录“当前子串的起始位置”和“子串长度”,不必做截取。节省空间。

    class Solution:
      # 暴力匹配(超时)
      def longestPalindrome(self, s: str) -> str:
          # 特判
          size = len(s)
          if size < 2:
              return s
    
          max_len = 1
          res = s[0]
    
          # 枚举所有长度大于等于 2 的子串
          for i in range(size - 1):
              for j in range(i + 1, size):
                  if j - i + 1 > max_len and self.__valid(s, i, j):
                      max_len = j - i + 1
                      res = s[i:j + 1]
          return res
    
      def __valid(self, s, left, right):
          # 验证子串 s[left, right] 是否为回文串
          while left < right:
              if s[left] != s[right]:
                  return False
              left += 1
              right -= 1
          return True
    

    思路2 动态规划

    因为回文串具有天然的状态转移,即对于一个长度大于2的回文串,去掉它首尾的两个字母后依然是个回文串。
    根据这样的思路我们可以用5. 最长回文子串 - 图1表示字符串 s 的第 i 到第 j 个字母组成的串是否为回文串

  • 状态: dp[i][j] 表示字串 s[i:j] 是否为回文子串

  • 状态转移方程:dp[i][j] = (s[i] == s[j]) and (j - i < 3 or dp[i+1][j-1]) ,所以dp[i][j]参考它左下方的值
  • 边界条件:5. 最长回文子串 - 图2,即5. 最长回文子串 - 图3
  • 初始化:dp[i][i] = true,但实际计算中对角线上的值并不会被参考
  • 输出:在得到一个状态的值为true的时候,记录起始位置和长度,等填表全部完成以后再截取
  • 答案:所有dp[i][j] = truej - i + 1(即字串长度)的最大值。

    class Solution(object):
      def longestPalindrome(self, s):
          """
          :type s: str
          :rtype: str
          """
          length = len(s)
    
          if length < 2:
              return s  # 特殊边界
    
          dp = [[False] * length for _ in range(length)]
          max_len = 1
          start = 0
    
          # 这个初始化后面并不会用到
          # for i in range(size):
          #     dp[i][i] = True
    
          for j in range(1, length):
              for i in range(0, j):
                  # 动态转移方程
                  if s[i] != s[j]:
                      dp[i][j] == False
                  else:
                      if j-i < 3:
                          dp[i][j] = True
                      else:
                          dp[i][j] = dp[i+1][j-1]
    
                  # 更新 max_len和start记录
                  if dp[i][j]:
                      cur_len = j - i + 1
                      if cur_len > max_len:
                          max_len = cur_len
                          start = i 
    
          # 填表全部结束后再截取
          return s[start : start+max_len]
    

    时间复杂度5. 最长回文子串 - 图4,其中n是字符串的长度。因为状态总数为5. 最长回文子串 - 图5,对于每个状态我们需要转移的时间为5. 最长回文子串 - 图6
    空间复杂度:5. 最长回文子串 - 图7,即存储状态需要的空间