"马拉车"是对manacher(算法作者)的音译,它的最基础的用途是以O(n)的时间复杂度求出一个字符串的最长回文子串(例如aabacda的最长回文子串是aba)
首先在处理回文问题的时候有一个技巧:由于回文串长度有可能为奇数也有可能为偶数,所以原生的回文子串中心不一定在一个字符上。可以在每两个字母之间插入一个特殊字符(比如‘#’),这样所有的回文串就都变成了以一个字符为回文中心
求最长回文子串的朴素做法是:
按回文串的中心位置进行枚举(即s[1]到s[n]),计算出每个回文串的最大长度,复杂度是O(n^2)
假设以s[i]为中心的最长回文串半径为r,(即由s[i-r]到s[i+r]这2r+1个字符组成的),可以得到这样的信息:
s[i-k]==s[i+k] (0<=k<=r)
s[i-r-1]!=s[i+r+1]
并且由于回文串的对称性,若已知以s[i-k]为中心的最长回文串b半径长度为len[i-k],则以s[i+k]为中心的最长回文串半径长度len[i+k]至少可以取到min(len[i-k],r-k)
于是当从左往右遍历枚举每个回文子串的中心位置时,若当前计算的位置s[i]被它左边的某个最长回文子串覆盖到(假设是以s[p]为中心),则s[i]不必从s[i+1]、s[i-1]开始判断,而可以直接从半径=min(len[2p-i],p+len[p]-i)开始判断
在算法中每个字符只会被匹配到一次,因此均摊复杂度是O(n)
而求所有回文子串的个数同样可以在马拉车的遍历过程中计算出来:
对于以s[i]为中心的最长回文子串来说,已s[i]为中心的所有回文子串的个数其实就是leni
例如abcba是最长回文子串,则以c为中心的回文串有3个:c bcb abcba
核心代码如下:
int mx=0,p=0,cnt=0;
memset(len,0,sizeof len);
for(int i=1;i<=n;i++)
{
if(mx>i)len[i]=min(mx-i,len[2*p-i]);
else len[i]=1;
while(s[i-len[i]]==s[i+len[i]])len[i]++;
if(len[i]+i>mx)mx=len[i]+i,p=i;
cnt+=len[i]/2;///除以2是因为这里的字符串之间插入了‘#’
}