Leetcode题5、最长回文字串(Python题解)

该博客介绍了如何用Python解决LeetCode第5题——找到给定字符串中最长的回文子串。文章提供了暴力解法、动态规划解法以及中心扩散法的详细解释,其中动态规划法可以通过优化降低空间复杂度到O(n)。虽然还涉及了Manacher算法,但博主表示因忙碌暂未深入讲解。

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

示例 1:

输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。

示例 2:

输入: “cbbd”
输出: “bb”

题目来源:力扣(LeetCode)

leetcode5.最长回文子串

难度:中等

分析

这道题的官方解法非常好,很清晰。

官方题解

解决方法
1:暴力解法,遍历

class Solution():
    def longestPalindrome(self,s):
        #边界条件,主要是要判断字符串不能为空,否则下标会溢出
        if s == s[::-1]: return s #
        max_length = 1 #初始化,要更新的字串长度
        res = s[0] #初始字串
        for i in range(0,len(s)-1):#遍历数组,注意下标
            for j in range(i+1,len(s)): #遍历,注意下标
                #判断是否存在新子串更长,如果存在,则更新最长字串长和最长字串
                #使用了python的切片方式来判断字串是否相同,导致时间复杂度O(n^2)
                #如果不用切片,则是否为回文仍需要一次遍历来判断,复杂度为O(n^3)
                if j-i+1 > max_length and s[i:j+1]==s[i:j+1][::-1]:
                    max_length = j-i+1 #更新最长字串长
                    res = s[i:j+1] #更新最长字串
        return res

复杂度:O(n^3)

在这里插入图片描述

2:动态规划法
回文字符串天生具有状态转移的性质,一个字符串是否为回文永远依赖于其内层字符串是否为回文。
因此考虑动态规划解法,这一题动态规划的难度在于将状态建表。理解了将求解过程转变为求状态,和建表的方法,这一解法就很简单了。
在这里插入图片描述

class Solution:
    def longestPalindrome(self, s: str) -> str:
        n = len(s)#字符串的长
        dp = [[False] * n for _ in range(n)]#构建一个矩阵,初始值全部为False
        #纵向是i坐标,横向是j坐标,因为j始终大于i所以这个矩阵只用填右上角
        ans = ""#记录最长回文字串
        #先升序填列,再升序填行
        for j in range(0,n):
            #升序降序填列都行,因为是前一列填好才对后一列进行求解
            for i in range(j,-1,-1): #for i in range(0,j+1):
                #先进行了两个判断,可以排除下标溢出
                #本质原因,动态规划需要初始条件,有内部是否为回文来决定外部是否是回文
                #初始条件1,最初始的字符是单字符,则一定构成字符串
                if j == i :dp[i][j]=True
                #初始条件2,最初始的字符是双字符,则是否为回文由这两个字符是否相等决定
                elif j - i == 1:
                    dp[i][j] = (s[i] == s[j])
                #d[i][j]代表的是有i到j的字符串是否为回文的状态
                #i到j是否为回文由i+1到j-1和新增的两个值决定
                #dp[i][j]的状态由dp[i + 1][j - 1]和s[i] == s[j]决定
                else:
                    dp[i][j] = (dp[i + 1][j - 1] and s[i] == s[j])
                if dp[i][j] and j-i+1>len(ans):
                    ans = s[i:j+1]
        return ans

在这里插入图片描述

复杂度:O(n^2),
空间复杂度也是O(n^2),但是可以优化到O(n)。

通过观察表,我们可以得到,我们只需要存储dp[i][j]前一列的值就可以计算dp[i][j]的状态,这样可以把空间复杂度降到O(n)。
这时,我们只需维护一列数据即可,因为需要不断更新这组数据,并且d[i][j]是由d[i+1][j-1]决定的。所以更新的时候要从上向下更新列数据。
在这里插入图片描述

class Solution():
    def longestPalindrome(self,s):
        n = len(s)
        line = [False]*n #维护一列数组,不断更新,道理相同。
        ans=""
        for j in range(0,n):
            for i in range(0,j+1):
                if j == i: line[i] = True
                elif j - i == 1:
                    line[i] = s[i]==s[j]
                else:
                    line[i] = line[i+1] and s[i]==s[j]
                if line[i] and j-i+1>len(ans):
                    ans = s[i:j+1]
        return ans

在这里插入图片描述
3:中心扩散法
顾名思义,遍历字符串,分别以字符串中的每个字符作为中心,以其为中心向两边寻找是否是回文。
需要注意的点是,回文中心有奇数中心和偶数中心两种情况。

class Solution():
    def expandAroundCenter(self,s, left, right):
        #从中心开始向两边依次检测是否相等
        while left >= 0 and right < len(s) and s[left]==s[right]:
            left -= 1
            right += 1
        return left+1, right-1
    def longestPalindrome(self,s):
        start, end =0, 0
        for i in range(len(s)):
            #中心扩散分为奇数中心和偶数中心,如"bab","baab"
            left1, right1 = self.expandAroundCenter(s,i ,i)
            left2, right2 = self.expandAroundCenter(s, i, i+1)
            #更新最长回文串首尾
            if right1 - left1 > end - start:
                start, end = left1, right1
            if right2 -left2 > end - start:
                start, end = left2, right2
        return s[start:end+1]

复杂度:O(n^2)
在这里插入图片描述

3:Manacher算法
是一个转为寻找最长回文字符串设计的复杂度为O(n)的算法。还没搞明白。先放官方解法。

class Solution:
    def expand(self, s, left, right):
        while left >= 0 and right < len(s) and s[left] == s[right]:
            left -= 1
            right += 1
        return (right - left - 2) // 2

    def longestPalindrome(self, s: str) -> str:
        end, start = -1, 0
        s = '#' + '#'.join(list(s)) + '#'
        arm_len = []
        right = -1
        j = -1
        for i in range(len(s)):
            if right >= i:
                i_sym = 2 * j - i
                min_arm_len = min(arm_len[i_sym], right - i)
                cur_arm_len = self.expand(s, i - min_arm_len, i + min_arm_len)
            else:
                cur_arm_len = self.expand(s, i, i)
            arm_len.append(cur_arm_len)
            if i + cur_arm_len > right:
                j = i
                right = i + cur_arm_len
            if 2 * cur_arm_len + 1 > end - start:
                start = i - cur_arm_len
                end = i + cur_arm_len
        return s[start+1:end+1:2]

复杂度:O(n)
太忙了最近,忙完回来填坑,官方用了十多分钟来讲这个算法。
官解:https://leetcode-cn.com/problems/longest-palindromic-substring/solution/zui-chang-hui-wen-zi-chuan-by-leetcode-solution/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值