5. 最长回文子串

51 阅读2分钟

题目描述

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

子串

如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。

 

示例 1:

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

示例 2:

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

 

提示:

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

思路

思路 1(掐头去尾法)

动态规划:因为求得是最长回文子串,子串意味着需要是连续的,我们可以记录 s[i,j] 是否为回文子串,因此可以使用 bool 类型的 dp 数组;(以 abcba 为例,假设 i = 0,j = 4,s[i] == s[j],那此时该子串是否为最长子串是否取决于 bcb 是否为最长子串,bcb 这个子串不就是 [i+1:j-1] 这个范围吗?)

当 s[i] == s[j] 时,如果 dp[i+1][j-1] = true 又或者该子串的长度 < 3(实际上就是 2,又加上 s[i] == s[j],所以它是回文子串),那么此时可以得出 s[i,j+1] 一定是回文子串;同时判断该子串是否比已经计算出的最长回文子串还要长,如果是的话,那么记录最长回文子串的起始下标和最长长度;

思路 2(中心扩散法)

利用回文性质,从一个点出发往两边扩散,记录形成的最长回文子串时的长度和起始位置。

需要注意的是,利用该方法需要处理一个特殊情况,以 abba 为例,如果我们分别从 0、1、2、3 出发进行最长回文判断得到的结果不是 4 而是 2,这是因为 bb 这种情况永远不会被考虑到。

代码

代码 1

func longestPalindrome(s string) string {
    // 求得是最长子串,必须是连续性有效,所以我们定义 bool 类型的 dp 
    // 表示以 start 为起点,以 end 为终点的子串是否为回文子串
    // if s[i] == s[j] => dp[i+1][j-1]
    var n, start, maxLen int   
    var dp [][]bool
    n = len(s) 
    dp = make([][]bool, n)
    for i := range dp {
        dp[i] = make([]bool, n)
        dp[i][i] = true
    }

    for i := n-2; i >= 0; i-- {
        for j := i+1; j < n; j++ {
            if s[i] == s[j] && (dp[i+1][j-1] || j-i+1 < 3) {
                dp[i][j] = true
                if j-i+1 > maxLen {
                    start = i
                    maxLen = j-i+1
                }
            }
        }
    }

    return s[start:start+max(maxLen ,1)]
}

图示略,可以使用二维数组模拟一下过程。

代码 2

func longestPalindrome(s string) string {
    var start, maxLen int
    maxLen = 1
    
    var helper func(i, j int)
    helper = func(i, j int) {
        for i >= 0 && j < len(s) && s[i] == s[j] {
            if maxLen < j-i+1 {
                maxLen = j-i+1
                start = i
            }
            i--
            j++
        }
    }

    for i := 0; i < len(s); i++ {
        helper(i, i)
        helper(i, i+1)
    }

    return s[start:start+maxLen]
}

图示

helper(i, i)

image.png

helper(i, i+1)

image.png