5. 最长回文子串

Posted 炫云云

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了5. 最长回文子串相关的知识,希望对你有一定的参考价值。

5. 最长回文子串

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

示例 1:

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

示例 2:

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

动态规划

对于一个子串而言,如果它是回文串,并且长度大于 2 , 那么将它首尾的两个字母去除之后,它仍 然是个回文串。例如对于字符串 “ababa”, 如果我们已经知道 “bab" 是回文串,那么 "ababa"一定 是回文串, 这是因为它的首尾两个字母都是 “a”。

第 1 步:定义状态

根据这样的思路,我们就可以用动态规划的方法解决本题。我们用 d P ( i , j ) dP(i, j) dP(i,j)​ 表示字符串 s s s​ 的第 i i i​ 到 j j j​ 个字母组成的串 (下文表示成 s [ i : j ] ) s[i: j]) s[i:j])​​ 是否为回文串:
d P ( i , j ) = {  true,   如果子串  S i … S j  是回文串   false,   其它情况  dP(i, j)= \\begin{cases}\\text { true, } & \\text { 如果子串 } S_{i} \\ldots S_{j} \\text { 是回文串 } \\\\ \\text { false, } & \\text { 其它情况 }\\end{cases} dP(i,j)={ true,  false,  如果子串 SiSj 是回文串  其它情况 
这里的「其它情况」包含两种可能性:

  • s [ i , j ] s[i, j] s[i,j] 本身不是一个回文串;
  • i > j i>j i>j, 此时 s [ i , j ] s[i, j] s[i,j]​ 本身不合法。

第 2 步:思考状态转移方程

那么我们就可以写出动态规划的状态转移方程:
d P ( i , j ) = d P ( i + 1 , j − 1 )   a n d   ( S i = = S j ) dP(i, j)=dP(i+1, j-1)~and ~\\left(S_{i}==S_{j}\\right) dP(i,j)=dP(i+1,j1) and (Si==Sj)
也就是说,只有 s [ i + 1 : j − 1 ] s[i+1: j-1] s[i+1:j1] 是回文串,并且 s s s 的第 i i i j j j 个字母相同时, s [ i : j ] s[i: j] s[i:j]​ 才会是回文串。

说明

  • 「动态规划」的「自底向上」求解问题的思路,很多时候是在填写一张二维表格。由于 s[i..j] 表示 s 的一个子串,因此 ij 的关系是 i <= j,只需要填这张表格对角线以上的部分;
  • 看到 dp[i + 1][j - 1] 就需要考虑特殊情况:如果去掉 s[i..j] 头尾两个字符子串 s[i + 1..j - 1] 的长度严格小于 2(不构成区间),即 j − 1 − ( i + 1 ) + 1 < 2 j - 1 - (i + 1) + 1 < 2 j1(i+1)+1<2 时,整理得 j − i < 3 j - i < 3 ji<3, 此时 s[i..j] 是否是回文只取决于 s[i]s[j] 是否相等。

第 3 步:考虑输出

上文的所有讨论是建立在子串长度大于 2 的前提之上的,我们还需要考虑动态规划中的边界条件, 即子串的长度为 1 或 2 。

  • 对于长度为 1 的子串,它显然是个回文串;
  • 对于长度为 2 的子串,只要它 的两个字母相同,它就是一个回文串。

根据这个思路,我们就可以完成动态规划了,最终的答案即为所有 d P ( i , j ) = dP(i, j)= dP(i,j)=​ true 中 j − i + 1 j-i+1 ji+1​ (即子 串长度) 的最大值。注意:在状态转移方程中,我们是从长度较短的字符串向长度较长的字符串进 行转移的,因此一定要注意动态规划的循环顺序。

class Solution:
    def longestPalindrome(self, s: str) -> str:
        n = len(s)
        if n < 2:
            return s
        
        max_len = 1
        begin = 0
        # dp[i][j] 表示 s[i..j] 是否是回文串
        dp = [[False] * n for _ in range(n)]
        for i in range(n):
            dp[i][i] = True
        
        # 递推开始 
        # 注意:先填左下角.填表规则:先一列一列的填写
        # 枚举右边界
        for j in range(n):
            # 确定左边界
            for i in range(j+1):
                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]

                # 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,此时记录回文长度和起始位置
                if dp[i][j] and j - i + 1 > max_len:
                    max_len = j - i + 1
                    begin = i
        return s[begin:begin + max_len]



class Solution:
    def longestPalindrome(self, s: str) -> str:
        n = len(s)
        dp = [[0] * n for _ in range(n)]
        res = ""
        for i in range(n):
            for j in range(i + 1):
                if s[i] == s[j] and (i - j  < 3 or dp[j+1][i-1]):
                    dp[j][i] = 1
                    res = max(res, s[j:i+1], key=len)   
        return res

参考

Krahets - 力扣(LeetCode) (leetcode-cn.com)

以上是关于5. 最长回文子串的主要内容,如果未能解决你的问题,请参考以下文章

leectcode 动态规划5.最长回文子串

LeetCode 5 最长回文子串

leetcode-5 最长回文子串(动态规划)

5. 最长回文子串

5-最长回文子串

[5]. 最长回文子串