5. 最长回文子串 Python-Leetcode
本文最后更新于 638 天前,其中的信息可能已经有所发展或是发生改变。

📕题干

中等

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

示例 1:

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

示例 2:

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

提示:

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

📖题解

法一

💻代码

class Solution:
    def longestPalindrome(self, s: str) -> str:
        a = ""
        for i in range(len(s),0,-1):
            for j in range(len(s)-i+1):
                a = s[j:j+i]
                x = 0
                while a==a[::-1]:
                    return a
执行用时:4052ms 消耗内存:16.43MB

🤔思路

直接暴力求解,从最长子串开始,逐个判断是否构成回文。

❗ 注意

本题做出来不难,但显然这样的思路仅仅能停留在做出来,显然执行用时太长。时间复杂度达到了O(n^3),因而要更进一步改进算法。

法二

💻代码

class Solution:
    def longestPalindrome(self, s: str) -> str:
        longest = ""
        for i in range(len(s)):
            l = min(i,len(s)-i-1)
            j = 0
            result1,result2 = "",""
            while j <= l:
                a = s[i-j:i+j+1]
                if a == a[::-1]:
                    result1 = a
                    j += 1
                else:
                    break
            l = min(i,len(s)-i)
            j = 0
            while j <= l:
                a = s[i-j:i+j]
                if a == a[::-1]:
                    result2 = a
                    j += 1
                else:
                    break
            if len(result1) > len(longest):
                longest = result1
            if len(result2) > len(longest):
                longest = result2

        return longest
执行用时:1134ms 消耗内存:16.50MB

🤔思路

由于回文子串有个特性,即若abcdcba是回文,则bcdcb一定也是回文。换句话说可以从短回文入手,再扩大范围,得到最长回文。比较根据字符串中各个字符产生的子串长度,得到最长的回文子串。

❗ 注意

此方法的执行时间明显下降,时间复杂度达到了O(n^2),但显然这个程度的时间还是过长,要进一步改进。

法三

💻代码

class Solution:
    def longestPalindrome(self, s: str) -> str:
        longest = ""
        def ifsidessame(left:int,right:int,result:str) -> str:
            if left < 0 or right >= len(s):
                return result
            elif left == right:
                result = s[left]
                return ifsidessame(left-1,right+1,result)
            elif s[left] == s[right]:
                result = s[left]+result+s[right]
                return ifsidessame(left-1,right+1,result)
            else:
                return result
        for i in range(len(s)):
            result1 = ifsidessame(i,i,"")
            result2 = ifsidessame(i,i+1,"")
            if len(result1) > len(longest):
                longest = result1
            if len(result2) > len(longest):
                longest = result2
        return longest
执行用时:733ms 消耗内存:16.82MB

🤔思路

我对法二中的拓展回文方法进一步优化,定义了ifsidesame函数,用来处理得到指定一个(奇数长度回文)或两个(偶数长度回文)字符时对应的最长回文,此时再遍历字符串,运用此函数便能对每一个字符返回最长子串,从而得出结果。

❗ 注意

此方法的执行时间任有下降,时间复杂度为O(n^2),但仍然不足。原因在于只是对原算法的优化,而要明显降低执行时间,需要重构。

法四

💻代码

class Solution:
    def longestPalindrome(self, s: str) -> str:
        if len(s) < 2:
            return s
        def ifsidesame(left: int, right: int) -> str:
            while left >= 0 and right < len(s) and s[left] == s[right]:
                left -= 1
                right += 1
            return s[left+1:right]
        longest = ""
        for i in range(len(s)):
            result1 = ifsidesame(i, i)
            result2 = ifsidesame(i, i + 1)
            longest = max(longest, result1, result2, key=len)
        return longest
执行用时:307ms 消耗内存:16.52MB

🤔思路

法三中采用递归的方式,而在法四中采用了迭代的方式。法三对字符串采用拼接,法四对字符串采用切片。这样的调整,可以降低执行时间。

📌没有了吗

到这里看似已经得到了最优解了,因为此时的执行时间在Leetcode的分布上已经很前面了。但是这就结束了吗❓

法五

💻代码

class Solution:
    def longestPalindrome(self, s: str) -> str:
        t = '#'.join(f'^{s}$')
        n = len(t)
        p = [0] * n
        center = right = 0
        for i in range(1, n - 1):
            p[i] = (right > i) and min(right - i, p[2 * center - i])
            while t[i + p[i] + 1] == t[i - p[i] - 1]:
                p[i] += 1
            if i + p[i] > right:
                center, right = i, i + p[i]
        max_len, center_index = max((n, i) for i, n in enumerate(p))
        start = (center_index - max_len) // 2  
        return s[start: start + max_len]

执行用时:84ms 消耗内存:16.57MB

🤔思路

在上述方法中,都涉及到了奇数回文串和偶数回文串的问题,为了统一解决方法,可以采用插入额外字符,如法五中的‘#’,并且通过'^''$'来标记原字符串的边界。

p[]来保存对应字符的回文半径,初始化center中心和right右边界。

在遍历过程中,能发现当iright左侧时,即i < right(i > center)恒成立。可以由对称性可知,在半径小于right-i时,此时i对应的回文串,应当与i'一致,由于实际半径p[i']可能大于或者小于right-i,所以可以取两者最小值作为预处理的p[i]

之后同之前的方法一样,对单个字符串在已有的p[i]基础上拓展出最长的回文串。由于遍历是从左至右,只有当下一个i的最右边界超过了已有的右边界,才能包含更多的未遍历数。便只需更新此时的centerright,而不必次次更新。

最终遍历得到p[]中最大的值与索引,最终计算出对应切片,返回。时间复杂度为O(n)

💡要点

  • 代码中第8行预处理格外重要,在测试下,没有经过预处理的代码块在Leetcode中实际执行时间为626ms,差距巨大。
  • 代码中第11行仅保存超右边界数值,在测试下,没有此行的代码块在Leetcode中实际执行时间为179ms,翻了一番。
  • 此代码中运用了Manacher’s Algorithm算法,此算法是用来查找一个字符串的最长回文子串的线性方法,可以使正常时间复杂度为O(n^2)的任务变为O(n)
暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇