题目:
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 1:
输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。
示例 2:
输入: “cbbd”
输出: “bb”
题解:
Manacher 算法,被中国程序员戏称为“马拉车”算法。专门用于解决“最长回文子串”问题,时间复杂度为 O(n)O(n),事实上,“马拉车”算法在思想上和“KMP”字符串匹配算法有相似之处,都避免做了很多重复的工作。“KMP”算法也有一个很有意思的戏称,带有一点颜色。
挺有意思的一件事情是:我在学习“树状数组”和“Manacher 算法”的时候,都看了很多资料,但最后代码实现的时候,就只有短短十几行。
理解 Manacher 算法最好的办法,是根据查阅的关于 Manacher 算法的文章,自己在稿纸上写写画画,举一些具体的例子,这样 Manacher 算法就不难搞懂了。
Manacher 算法本质上还是中心扩散法,只不过它使用了类似 KMP 算法的技巧,充分挖掘了已经进行回文判定的子串的特点,提高算法的效率。
下面介绍 Manacher 算法的运行流程。
首先还是“中心扩散法”的思想:回文串可分为奇数回文串和偶数回文串,它们的区别是:奇数回文串关于它的“中点”满足“中心对称”,偶数回文串关于它“中间的两个点”满足“中心对称”。为了避免对于回文串字符个数为奇数还是偶数的套路。首先对原始字符串进行预处理,方法也很简单:添加分隔符。
第 1 步:对原始字符串进行预处理(添加分隔符)
我们先给出具体的例子,看看如何添加分隔符。
例1:给字符串 "level"
添加分隔符 “#”。
答:字符串 "level"
添加分隔符 "#"
以后得到:"#le#v#e#l#"
。
例2:给字符串 "noon"
添加分隔符 "#"
。
答:字符串 "noon"
添加分隔符 "#"
以后得到:"#n#o#o#n#"
。
我想你已经看出来分隔符是如何添加的,下面是两点说明。
1、分隔符是一定是原始字符串中没有出现过的字符,这个分隔符的种类也只能有一个,即你不能同时添加 "#"
和 "?"
作为分隔符;
2、添加分隔符的方法是在字符串的首位置、尾位置和每个字符的“中间”都添加 11 个这个分隔符。可以很容易知道,如果这个字符串的长度是 len
,那么添加的分隔符的个数就是 len + 1
,得到的新的字符串的长度就是 2 * len + 1
,显然它一定是奇数。
为什么要添加分隔符?
1、首先是正确性:添加了分隔符以后的字符串的回文性质与原始字符串是一样的(这句话不是很严谨,大家意会即可);
2、其次是避免回文串长度奇偶性的讨论(马上我们就会看到这一点是如何体现的)。
第 2 步:得到 p 值
p值为会文的最大半径:以 char[i] 作为回文中心,同时向左边、向右边进行“中心扩散”,直到“不能构成回文串”或“触碰到字符串边界”为止,能扩散的步数 + 1(这个 1 表示“中心”) ,即定义为 p 数组的值,也称之为“回文半径。
不断的循环迭代中心点,如果新的半径大于原来半径,那么就令p等于新的半径,并记录这个中心点的索引。
有了索引,有了最大半径,那么就可以得到最大回文字符串.
code:
class Solution: def longestPalindrome(self, s: str): s = '#' + '#'.join(s) + '#' s_len = len(s)-1 index = 0 p=0 for i in range(1, len(s)): j=1 while True: if i<j or i+j>s_len or s[i-j]!=s[i+j] : if j-1>p: p = j-1 index = i break j+=1 return ''.join(s[index-p:index+p+1].split('#'))