5. 最长回文子串

最长回文子串

题目描述

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

示例 1:
  • 输入: “babad”
  • 输出: “bab”
  • 注意: “aba” 也是一个有效答案。
示例 2:
  • 输入: “cbbd”
  • 输出: “bb”

题解

所谓回文子串,首先是一个字符串s 的 子串(如,sl...srs_l,...,s_r)而且是连续的;其次是回文,就是正着读和倒着读该字符串都是一样的。

方法一: 暴力

  • 时间复杂度: O(n3n^3)
  • 空间复杂度: O(nn)
思路

枚举该字符串的每一个子串,并用O(n)的时间复杂度来判断该子串是否是一个回文串。思路很简单,但是很耗时间。

python代码
class Solution:
    def longestPalindrome(self, s: str) -> str:
        n = len(s)
        ans = [1, 0, 1]  # 最长回文子串的长度, 最长回文子串的左边界, 右边界
        for i in range(n):
            for j in range(i, n):
                if j-i+1<=ans[0]: continue
                i0, j0 = i, j
                yes = True
                while i0<=j0:
                    if s[i0] != s[j0]:
                        yes = False
                        break
                    i0 += 1
                    j0 -= 1
                if yes and j-i+1 > ans[0]:
                    ans = [j-i+1, i, j+1]
        return s[ans[1]: ans[2]]

方法二:动态规划(区间DP)

  • 时间复杂度:O(n2n^2)
  • 空间复杂度:O(n2n^2)
思路

想在整个字符串得出最长回文子串,我们无法一下得出结果。但是,我们可以很容易的求出小字符串是否是回文串。比如:“aa”、“asa”。像这样的我们可以一眼就能看出他们是不是回文串。在此基础上,我们可以很轻松判断他们是不是回文串,如:“caac”、“caa”、“wasad”。然后再用一个二维数组来存储每一个子串是否是回文串。现在我们就只需要判断两边的字符是否相等以及里面的字符串是否是回文串来判断长的字符串是否是回文串。由小串逐渐推向大串,最终得到整个字符串的最长回文子串。结果存储在dp数组中。经过一次遍历求出最长回文串。
dp[l][r] : 表示子字符串sl,sl+1,...,sr1,srs_l, s_{l+1}, ...,s_{r-1} , s_r 是否是回文串。0表是不是;1表示是。递推公式如下:

dpl,r={1if:s[l]==s[r]len<30if:s[l]!=s[r]len<3dpl1,r+1if:s[l]==s[r]0if:s[l]!=s[r] \begin{aligned} dp_{l, r}=\left\{ \begin{array}{clc} 1 & & if:s[l] == s[r]、len < 3\\ 0 & & if:s[l] != s[r] 、len < 3\\ dp_{l-1, r+1} & & if:s[l] == s[r]\\ 0 & & if:s[l] != s[r] \end{array} \right. \end{aligned}

  • 当子字符串的长度小于3并且两端字符串相等时,就表明子串是回文串!
  • 当子字符串的长度大于等于3时,如果两端字符串相等,那该子串是否回文就取决于去掉两端后的子串;如果不等,直接赋值为零。
Python代码
class Solution:
    def longestPalindrome(self, s: str) -> str:
        n = len(s)
        if n == 0: return ''  # 当输入为空串的时候,输出也为空串
        dp = [[0 for _ in range(n)] for _ in range(n)]  # 不要使用dp[[0]*n]*n
        for i in range(n):  # 对dp数组进行预处理
            dp[i][i] = 1

        for i in range(2, n + 1):  # 子字符串的长度
            for l in range(n):  # 遍历左端
                if l + i > n: break
                r = l + i - 1  # 右端
                if s[l] == s[r]:
                    if i == 2 or i == 3:
                        dp[l][r] = 1
                        continue
                    dp[l][r] = dp[l + 1][r - 1]
                else:
                    dp[l][r] = 0
        
        for i in range(n, 0, -1):  # 子字符串的长度
            for l in range(n):
                if l + i > n: break
                r = l + i - 1
                if dp[l][r] == 1:
                    return s[l: r+1]  # 返回结果

因为我对python还不是很了解,所以写程序的时候我碰到了一个很大的坑。就是我在初始数组dp的时候是这样写的dp[[0]*n]*n。但是当我预处理时。数组中所有的值都变成1了。有知道为啥的可以教教我。

方法三:

  • 时间复杂度:O(n2n^2)
  • 空间复杂度:O(11)
思路

在方法一中,我们枚举每个字符串的时候是以左右两边界枚举的。这样枚举虽然可以把每一个子字符串都枚举出来。但是那些不是回文串的也被无用的枚举。但是我们假设我们枚举的都是回文串。以他们的中心轴为标准进行枚举。就可以时间复杂度从O(n3n^3)降到O(n2n^2)。

python代码
class Solution:
    def longestPalindrome(self, s: str) -> str:
        n = len(s)
        if n == 0: return ''
        ans = [1, -1, 1]
        for i in range(n):  # 以字符s[i]/s[i-1]S[I]为中心轴
            l = i-1
            r = i+1
            while r<n and s[r] == s[i]:
                r += 1
            while l>=0 and r<n and s[l] == s[r]:
                l -= 1
                r += 1
            if ans[0] < (r-l-1):
                ans = [r-l-1, l, r]

        return s[ans[1]+1: ans[2]]

方法四:Manacher算法(马拉车算法)

  • 时间复杂度:O(nn)
  • 空间复杂度:O(nn)
思路
python代码
class Solution:
    def longestPalindrome(self, s: str) -> str:
        if len(s) < 2: return s

        def get_str(s):  # 预处理字符串
            str = '$#'  # 以防数组越界
            str += '#'.join(list(s))
            str += '#'
            return str
        def mancher(s):
            n = len(s)
            R, C = 0, 0  # 最右回文右边界R, 最右回文右边界的对称中心C
            l, r, maxlen = 0, 0, 0  # 最大回文串的左右边界和最大长度
            radius = [0] * n  # 回文半径数组radius
            for i in range(n):
                if R > i:
                    radius[i] = min(R - i, radius[2 * C - i])
                else:
                    radius[i] = 1
                while i + radius[i] < n and i-radius[i] >= 0 and s[i + radius[i]] == s[i - radius[i]]: radius[i] += 1
                if radius[i] + i > R:
                    R = radius[i] + i
                    C = i
                    if maxlen < radius[i]:
                        maxlen = radius[i]
                        l = i-radius[i]+1 # 因为此时的radius[i]比实际符合条件的要大1, 所以左边界要加1,右边界不用加因为返回时右边界是开区间。
                        r = i+radius[i]
            return s[l: r]
        
        return mancher(get_str(s))[1::2]  # 去掉中间的 ‘#’
完整python代码
class Solution:
    def longestPalindrome(self, s: str) -> str:
        if len(s) < 2: return s

        def get_str(s):  # 预处理字符串
            str = '$#'  # 以防数组越界
            str += '#'.join(list(s))
            str += '#'
            return str
        def mancher(s):
            n = len(s)
            R, C = 0, 0  # 最右回文右边界R, 最右回文右边界的对称中心C
            l, r, maxlen = 0, 0, 0  # 最大回文串的左右边界和最大长度
            radius = [0] * n  # 回文半径数组radius
            for i in range(n):
                if R > i:
                    radius[i] = min(R - i, radius[2 * C - i])
                else:
                    radius[i] = 1
                while i + radius[i] < n and i-radius[i] >= 0 and s[i + radius[i]] == s[i - radius[i]]: radius[i] += 1
                if radius[i] + i > R:
                    R = radius[i] + i
                    C = i
                    if maxlen < radius[i]:
                        maxlen = radius[i]
                        l = i-radius[i]+1
                        r = i+radius[i]
            return s[l: r]
        
        return mancher(get_str(s))[1::2]  # 去掉中间的 ‘#’

def stringToString(input):
    return input[1:-1].decode('string_escape')

def main():
    import sys
    import io
    def readlines():
        for line in io.TextIOWrapper(sys.stdin.buffer, encoding='utf-8'):
            yield line.strip('\n')

    lines = readlines()
    while True:
        try:
            line = next(lines)
            s = stringToString(line);
            
            ret = Solution().longestPalindrome(s)

            out = (ret);
            print(out)
        except StopIteration:
            break

if __name__ == '__main__':
    main()
发布了51 篇原创文章 · 获赞 98 · 访问量 6564
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章