Bootstrap

最长回文子串(动态规划),像讲故事一样进行求解,基于python实现

希望能把算法像故事一样讲出来~

希望大佬们赏个赞,谢谢~

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

思路来自leetcode官方:

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

根据这样的思路,我们就可以用动态规划的方法解决本题。我们用 P(i,j)表示字符串 s的第 i到 j个字母组成的串(下文表示成 s[i:j])是否为回文串:

 

这里的「其它情况」包含两种可能性:

s[i,:j]本身不是一个回文串;

i > j,此时 s[i:j] 本身不合法。

那么我们就可以写出动态规划的状态转移方程:

也就是说,只有 s[i+1:j-1]是回文串,并且 s 的第 i和 j个字母相同时,s[i:j]才会是回文串。

上文的所有讨论是建立在子串长度大于 2 的前提之上的,我们还需要考虑动态规划中的边界条件,即子串的长度为 1 或 2。对于长度为 1的子串,它显然是个回文串;对于长度为 2的子串,只要它的两个字母相同,它就是一个回文串。因此我们就可以写出动态规划的边界条件:

根据这个思路,我们就可以完成动态规划了,最终的答案即为所有 P(i,j)=true 中 j-i+1(即子串长度)的最大值。

以上是思路,转载于leetcode官方,下面我尽可能地将这段代码注释地足够细节,因为我也是小白,所以希望帮助其他小白进行理解,希望各位大佬批评指正。

class Solution:
    def longestPalindrome(self, s: str) -> str:
        #首先获取字符串s的长度
        n = len(s) 
        #给最大回文子串的长度定义一个初始值1,想一想,如果没有更长的回文子串,那么最大回文子串就是单个字符(其也符合回文的要求)
        max_len = 1 
        #定义子串的左边界为begin,并赋予初值0
        begin = 0
        #判断字符串长度是否小于2,若小于2,那就是单个字符,其最大回文子串就是它自身,直接返回
        if n < 2:
            return s
        #定义一个n*n的二维数组dp,这是用来存储从i到j处的子串s[i:j]是否为回文子串的,显然有n*n个s[i:j],dp中的值全是True和False,True代表s[i:j]回文,先将dp中所有元素初始化为False,后面按条件填充为True
        dp = [[False] * n for iter in range(n)]
        #容易知道,任意一个字符都可看做回文子串,这里s[i:i]代表的是所有的单个字符x,将其在dp中对应的位置置为True
        for i in range(n):
            dp[i][i] = True
        #遍历所有可能的子串长度,前面我们已经排除掉了长度为1的情况,这里就从2开始遍历到n即可
        for L in range(2,n+1):
            #接着遍历左边界,左边界就让它从0开始遍历到n-1,从左到右的每个位置都可以作为左边界
            for i in range(n):
                #那么这个右边界就可以直接由L=j-i+1推出来了
                j = L + i - 1
                #紧接着,判断j有没有大于等于n,试想若等于n,那索引就超出字符串的范围了,最右端索引才是n-1而已,说明右边界已经遍历到头了,这时候直接跳出内循环,换个子串长度继续循环
                if j >= n:
                    break
                #那如果第i个字符和第j个字符不相等呢?首尾不同,那直接就可以判断i:j这个子串就不可能回文了,所以将dp[i][j]置为False
                if s[i] != s[j]:
                    dp[i][j] = False
                #否则的话,就说明首尾字符相同,那这个子串是回文子串就等价于去掉首尾的子串回文这一问题了
                else:
                    #但是话虽然这样说,还有一种特殊情况可以先处理掉,那就是i:j只有3个字符,那去掉首尾,中间就只有一个字符了,这必然回文啊,就直接将dp置为True好了
                    if j - i < 3:
                        dp[i][j] = True
                    #去掉了特殊情况,剩下的情况就用上面说的递推思想往夹心里的子串上递推就行了
                    else:
                        dp[i][j] = dp[i+1][j-1]
                #找完了所有的情况了,我们要来找出最长回文子串啦,啥时候最长回文?首先你要回文吧,这就要求这个子串的dp是True,其次我要让这个回文子串的长度与之前设置的最大回文子串长度的初始值进行比较,然后如果当前回文子串长度更大,就把最大回文子串这个头衔颁给当前回文子串,毕竟上面的算法会遍历完所有的回文子串嘛,所以只要循环结束了,也就把最大回文子串这个头衔颁给了那个长度最长的回文子串了,再把该回文子串的初始位置赋给begin
                if dp[i][j] and j - i + 1 > max_len:
                    max_len = j - i + 1
                    begin = i
        #直接返回这个回文子串就可以了,他就是大名鼎鼎的最长回文子串!!!
        return s[begin:begin+max_len]

;