马拉车(manacher)算法是在求回文子串时,相对较快的一种算法
最普通的一种算法是,对给定的字符串string s
, 将[0, s.length() - 1]
分别作为中心,向两边延伸,直到两边的字符不相等,同时更新最大长度和中心点
由于上述算法还要考虑单数偶数的情况,因此可以在遍历前,手动给s
添加分隔符'#'
, 比如123
变为#1#2#3#
,这样一来得到的字符串一定是奇数的,同样遍历一遍,得到最大长度和中心点,即可得到回文子串
但这样的时间复杂度为O(n^2)
,这时就又延伸出一个马拉车算法
网上已有很多代码与讲解,我在自认为理解之后写下我的理解:
首先是核心代码部分:
int mx = 0, id = 0;
int maxC = 0, maxL = 1;
vector<int> p(ss.length(), 0);
for (int i = 1; i < ss.length(); i++)
{
p[i] = (mx > i ? min(p[2 * id - i], mx - i) : 1);
while (ss[i + p[i]] == ss[i - p[i]]) p[i]++;
if (mx < i + p[i])
{
mx = i + p[i];
id = i;
}
if (p[i] > maxl)
{
maxL = p[i];
maxC = i - p[i];
}
}
而其中最为核心,也最需要理解的一行就是p[i] = (mx > i ? min(p[2 * id - i], mx - i) : 1);
这里用到的变量名都是延续网上用的比较多的
先说字符串ss
,这是已经加了分隔符,同时为了防止越界,人为增加另一个特殊且一定不会在原字符串中出现字符的字符串,如123
变为$#1#2#3#
, 若不加$
,只要在while
循环中增加约束条件防止越界即可
数组p
,p[i]
代表的是以ss中第i位为中心的回文字符串的半径
如$#c#b#a#b#d#
, 以a为中心的最大回文子串半径为4,以d为中心半径为2
这里的半径与原字符串的对应关系为,ss[i]的半径=s[i]的回文字串长-1
如$#c#b#a#b#d#
, 以a为中心的最大回文子串半径为4,对应到原字符串为bab,因此长为3=4-1
mx
是目前以统计到的回文串能延伸到的最右端的位置,id
则是该最右端位置对应的字符串中心
如图所示,假设此时在求以i
为中心的回文子串,已有p[id] + id = mx
如果i
在mx
右边, 说明目前还未遍历到i
这个位置,此时就跟最前面介绍的算法一样,初始化半径为1,即只有ss[i]字符本身,之后暴力解即可,最后更新mx
的位置
若i
在mx
左边,此时又有两种情况:
首先看一个例子(没加分隔符):abcdhdcefecdhdcen
当遍历到i=8
, 即s[i]='f'
时,mx
被更新为14,指向'c', p[8]=7
此时往后遍历,当遍历到i=12,s[i]='h'
,因为已经通过前面的遍历知道[2, 14]的字符串是回文字符串
那么以8为中心,12的对应位置4,p[4]=3
, 如果p[4]+12 <= mx=14
,那么可以直接认定p[12]=p[4]
而如果p[4]+12 > mx=14
,那么只能确定p[12
]至少有14-12=2这么长,后面的字符还没遍历到,则同样采用最前面的算法,往两边遍历直至跳出循环,并更新mx和id
上面的4对应j,8对应id,12对应i, 14对应mx
, 即可对应到代码中
放一道leetcode例题 5. Longest Palindromic Substring 的答案
class Solution {
public:
string longestPalindrome(string s)
{
string ss = "$#";
for (int i = 0; i < s.length(); ++i)
{
ss += s[i];
ss += '#';
}
int mx = 0, id = 0;
// maxC记录中心,maxL记录长度
int maxC = 0, maxL= 1;
vector<int> p(ss.length(), 0);
for (int i = 1; i < ss.length(); i++)
{
p[i] = (mx > i ? min(p[2 * id - i], mx - i) : 1);
while (ss[i + p[i]] == ss[i - p[i]]) p[i]++;
if (mx < i + p[i])
{
mx = i + p[i];
id = i;
}
if (p[i] > maxL)
{
maxL= p[i];
maxC= i;
}
}
return s.substr((maxC-maxL) / 2, maxL- 1);
}
};