1 题目描述
2 解题思路
2.1 动态规划
对于一个子串而言,如果它是回文串,并且长度大于 2,那么将它首尾的两个字母去除之后,它仍然是个回文串。
例如对于字符串 “ababa”,如果我们已经知道 首尾元素都是回文串,那么“bab” 一定是回文串。
也就是说,如果 s[i+1:j−1] 是回文串,并且 s[i]=s[j],那么s[i:j] 是回文串。
上文的所有讨论是建立在子串长度大于 2 的前提之上的,我们还需要考虑动态规划中的边界条件,即子串的长度为 1 或 2。
对于长度为 1 的子串,它显然是个回文串。
对于长度为 2 的子串,只要它的两个字母相同,它就是一个回文串。
因此我们就可以写出动态规划的边界条件:
根据这个思路,我们就可以完成动态规划了,最终的答案即为所有P(i,j)=true 中j−i+1(即子串长度)的最大值。
class Solution:
def longestPalindrome(self, s: str) -> str:
n=len(s)
if(len(s)<2):
return s
#如果字符串有一个或者零个元素,那么这个字符串肯定是回文串
maxLen=1
#最长回文子串的长度
begin=0
#最长回文子串的起始位置
dp=[[False]*n for _ in range(n)]
#dp是一个n*n的数组,dp[i][j]表示[i,j]部分的s是不是回文串
for i in range(n):
dp[i][i]=True
#[i,i]部分(也就是子串长度为1)一定是回文串
for L in range(2,n+1):
#从小到大枚举子串的长度(长度为1的都是True,前面已经考虑过了)
for i in range(n):
#遍历起始位置
j=i-1+L
#j-i+1=L -> j=i-1+L
#我们目前考虑的字串的终止位置
if(j>=n):
break
#如果终止位置超过边界了,那么我们就考虑更长一格的字符串,再从坐标0开始考虑;
#当前字符串长度停止继续遍历起始位置(因为之后起始位置对应的终止位置也是越界的)
#因为我们是左闭右闭,所以终止条件是j≥n而不是j>n
if(s[i]!=s[j]):
pass
#如果起止元素不相等,那么[i,j]不是回文串
else:
if(L==2 or L==3):
dp[i][j]=True
#如果此时我们考虑的字符串长度是2或者3,那么起止元素相等后,
#中间只”夹“着零个或者一个元素,那么此时[i,j]是回文数
else:
dp[i][j]=dp[i+1][j-1]
#不然的话,[i,j]是不是回文数取决于[i+1,j-1]是不是回文数
if(dp[i][j]==True and L>maxLen):
maxLen=L
begin=i
#如果此时我们遍历到的回文子串长度比目前为止遍历到的回文子串长,
#那么更新此时的回文子串起始位置和长度
return s[begin:begin+maxLen]
2.2 中心扩展
我们观察一下2.1中的状态转移方程
我们可以看到其中的状态转移链:
可以发现,所有的状态在转移的时候的可能性都是唯一的。也就是说,我们可以从每一种边界情况开始「扩展」,也可以得出所有的状态对应的答案。
边界情况即为子串长度为 1或 2 的情况。我们枚举每一种边界情况,并从对应的子串开始不断地向两边扩展。如果两边的字母相同,我们就可以继续扩展,例如从 P(i+1,j−1) 扩展到 P(i,j);如果两边的字母不同,我们就可以停止扩展,因为在这之后的子串都不能是回文串了。
我们枚举所有的长度为1或者2并尝试「扩展」,直到无法扩展为止,此时的最长回文串即是最终的答案。
class Solution:
def expand_around_center(self,s,left,right):
l=len(s)
while(left>=0 and right<=l-1 and s[left]==s[right]):
#如果此时子串的左边界和右边界没有越界,而且我们此时左边界和右边界的值是相等的
#那么我们将考虑的子串向外扩张“一圈”,继续讨论。如此往复,直到至少不满足这三个条件的一个
left=left-1
right=right+1
return(left+1,right-1)
#因为我们最后考虑的左右边界是至少不满足三个条件之一的,
#所以我们左边界右边界要向内“缩一格”,此时的子串是我们当前扩展到的最大回文串
def longestPalindrome(self, s: str) -> str:
start=0
end=0
#start,end是我们最长回文子串的起止位置
l=len(s)
for i in range(l):
#遍历所有初始回文子串的左边界
left1,right1=self.expand_around_center(s,i,i+1)
left2,right2=self.expand_around_center(s,i,i)
if(right1-left1>end-start):
end=right1
start=left1
if(right2-left2>end-start):
end=right2
start=left2
return(s[start:end+1])