解法
记一下一题多解,求最长回文子串
解法一:最长公共子串
如果想用S
和S[::-1]
的最长公共子串来求最长回文子串,会有一个问题就是:当S=abcdfcba
和S[::-1]=abcfdcba
时,最长的公共子串是abc
,它显然不是回文。
解决方法是在dp的时候判断一下公共子串的下标是否对应
但是这个方法效率不高,反正我超时了
class Solution(object):
def longestPalindrome(self, s):
"""
:type s: str
:rtype: str
"""
t = s[::-1]
n = len(s)
dp = [0]*n
ans = ""
for i in xrange(n):
dp[i] = dp[i-1]
for j in xrange(i,-1,-1):
l = 0
while i>=l and j>=l and s[i-l]==t[j-l]:
l += 1
if dp[i]<l:
if i+j-l+1==n-1:
dp[i] = l
ans = s[i-l+1:i+1]
l = 0
while i>=l and j>=l and t[i-l]==s[j-l]:
l += 1
if dp[i]<l:
if i+j-l+1==n-1:
dp[i] = l
ans = t[i-l+1:i+1]
return ans
解法二:暴力
就是枚举每对(i,j)
对,判断是不是回文
解法三:DP
相当于是有记忆的暴力,(i,j)
是不是回文取决于(i+1,j-1)
是不是,并且s[i]
是不是等于s[j]
class Solution(object):
def longestPalindrome(self, s):
"""
:type s: str
:rtype: str
"""
n = len(s)
dp = [[False]*n for _ in xrange(n)]
ans = ""
for i in xrange(n):
dp[i][i] = True
if len(ans)<len(s[i]):
ans = s[i]
for l in xrange(1,n):
for i in xrange(n-l):
dp[i][i+l] = (2>l or dp[i+1][i+l-1]) and s[i]==s[i+l]
if dp[i][i+l]:
ans = s[i:i+l+1]
return ans
解法四:扩展
之前想到过这种,每个回文串的“种子”是所有长度为1的串
和所有长度为2且相同的串
,然后不断向两边扩展
class Solution(object):
def longestPalindrome(self, s):
"""
:type s: str
:rtype: str
"""
n = len(s)
cand = set()
ans = ""
for i in xrange(n):
cand.add((i,i))
if i<n-1 and s[i]==s[i+1]:
cand.add((i,i+1))
while cand:
nextc = set()
for i,j in cand:
if len(ans)<=j-i:
ans = s[i:j+1]
if i>0 and j<n-1 and s[i-1]==s[j+1]:
nextc.add((i-1,j+1))
cand = nextc
return ans
解法五:Manacher算法
- 首先把所有的回文子串统一为奇数长度,具体如何做呢?把字符之间加上分隔符
#
,如bob
加上后变#b#o#b#
长为奇数,moom
变成#m#o#o#m#
长度为奇数,所以问题就变成找长度最长的奇数的字符串了 - 奇数的字符串可以由一个二元组表示
(i,r)
,其中i为字符串中心的坐标,r为从中心延伸出去的长度 - 接下来就是算法的精髓了:
假设rm
为右边界在最右边的回文子串的中心,p[rm]
为它的半径,我们现在遍历到中心i
,当然i
大于rm
,并且小于i
的所有中心的半径都已经知道了
我们可以求出i
关于rm
的对称点j=2*rm-i
,它在rm
左边并且有着准确的半径p[j]
-
如果
i<=rm+p[rm]
,那么i
在rm
的回文子串内,同时j
也在。- 如果
p[j]<=rm+p[rm]-i
,那么j
的回文子串整个都在rm
的回文子串里,如下图,由对称性可知,p[i]=p[j]
。
- 如果
p[j]>rm+p[rm]-i
,那么j
的回文子串有一部分在rm
的回文子串里,有一部分在rm
的回文子串外,至少在rm
的回文子串里的那部分是相同的,在rm
外的就只能自己遍历确定了。如果新的边界大于rm
的边界,还需要更新。
- 如果
-
如果
i>rm+p[rm]
,那么也只能遍历来确定了,并且这个时候一定需要更新rm
-
class Solution(object):
def longestPalindrome(self, s):
"""
:type s: str
:rtype: str
"""
n = len(s)
n = 2*n+1
p = [0]*n
rm = 0
def same(i,j):
if i%2==0 and j%2==0:
return True
if i%2!=0 and j%2!=0 and s[i//2]==s[j//2]:
return True
return False
ans = ""
for i in xrange(1,n):
if i<=rm+p[rm]:
j = 2*rm-i
p[i] = min(p[j], rm+p[rm]-i)
while i+p[i]<n-1 and i-p[i]>0 and same(i+p[i]+1, i-p[i]-1):
p[i]+=1
else:
while i+p[i]<n-1 and i-p[i]>0 and same(i+p[i]+1, i-p[i]-1):
p[i]+=1
if i+p[i]>rm+p[rm]:
rm = i
b = (i-p[i])//2
e = (i+p[i]+1)//2
if len(ans)<e-b:
ans = s[b:e]
return ans
速度快了一个数量级= =