问题:
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 1:
输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。
示例 2:
输入: “cbbd”
输出: “bb”
题目来源:力扣(LeetCode)
难度:中等
分析:
这道题的官方解法非常好,很清晰。
解决方法:
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/
该博客介绍了如何用Python解决LeetCode第5题——找到给定字符串中最长的回文子串。文章提供了暴力解法、动态规划解法以及中心扩散法的详细解释,其中动态规划法可以通过优化降低空间复杂度到O(n)。虽然还涉及了Manacher算法,但博主表示因忙碌暂未深入讲解。
&spm=1001.2101.3001.5002&articleId=107597363&d=1&t=3&u=e62b69b00a3f43d3b8385ff08760e383)
673

被折叠的 条评论
为什么被折叠?



