给定一个字符串 s
,找到 s
中最长的回文子串。你可以假设 s
的最大长度为 1000。
示例 1:
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
示例 2:
输入: "cbbd"
输出: "bb"
解题思路:
manacher(马拉车算法)
马拉车是专门处理回文字符子串的一种算法。
首先,回文字符串有两种:长度为奇数,"aba";长度为偶数,"abba"。偶数长度的回文字符串不是以字符为对称中心,而马拉车算法通过对字符串的每个字符之间(包括首尾两端)插入一个特殊符号,如#(
这个符号必须是原字符串中所没有的),来统一了奇偶的情况(#a#b#a#, #a#b#b#a#,长度均为奇数)。
其次,很好的利用了回文字符串的性质,核心公式:p[i] = min(max_right-i, p[2*center-i])
解读这个公式之前我们先初始化几个变量:
p :初始化全为0的列表,用来记录以当前位置为中心的最长的回文子串的半径
center :最长回文子串中心点
max_right :最长回文子串最远位置
max_s = 0 :最长回文子串中心点所在地的位置
现在,假设我们已经求得了p[0, 1 , 2.....i-1],那么对于第i个位置来说:
首先要判断 i 的位置在max_right的右侧还是左侧:
当i < max_right,就再找到j(j 是 i 关于当前最长回文子串的中心点center的对称点),这里就利用了回文字符串的性质,
如果p[j] <= max_right-i,也就是说i加上j这一点的回文半径的长度还没有超过max_right,那么i这一点的回文半径大于等于p[j],再继续暴力向外搜索,找到该点的最长回文子串长度;
如果p[j] > max_right-i,那么对于max_right内的点就一定是对称的,则从max_right点再向外暴力搜索;
当i >= max_right,就无法利用回文字符串的特点,直接暴力搜索。
最后再更新一下最长字符子串和中心点。
综上,马拉车算法利用回文子串的性质将时间复杂度从O(N^2)降到了O(N),很抽象比较难理解,看代码应该会好很多
代码:
class Solution:
def longestPalindrome(self, s: str) -> str:
s = "!#" + "#".join(s) + "#?"
center = 0 #最长回文子串中心点
max_right = 0 #最长回文子串最远位置
max_s = 0 #最长回文子串中心点所在地的位置
p = [0] * (len(s)-1)
for i in range(1, len(s)-1):
if i < max_right:
p[i] = min(max_right-i, p[2*center-i])
else:
p[i] = 1
while i-p[i]>0 and i+p[i]<len(s) and s[i-p[i]]==s[i+p[i]]:
p[i]+=1
if i+p[i] > max_right:
max_right = i+p[i]
center = i
max_s = max(max_s, p[i])
s=s[p.index(max_s)-(max_s-1): p.index(max_s)+(max_s-1)]
s=s.replace('#','')
return s
最后再附一下暴力解法,很暴力很暴力。。。。。
class Solution:
def countSubstrings(self, s: str) -> int:
num = []
for k in range(2, len(s)+1): #k:滑动窗口的长度
for i in range(len(s)-(k-1)):
st = s[i:i+k]
n = 0
if len(st)%2 == 0:
for j in range(int(len(st)/2)):
if st[j] == st[len(st)-1-j]:
n+=1
if n == int(len(st)/2):
num.append(st)
else:
for j in range(math.floor(len(st)/2)):
if st[j] == st[len(st)-1-j]:
n+=1
if n == math.floor(len(st)/2):
num.append(st)
return len(num) + len(s)