Manacher算法/O(n)时间复杂度求字符串中最长回文子字串算法
刷leetcode的5. Longest Palindromic Substring时被虐的要死要活的……找了一下才发现历史上已有存在的最优算法,manacher(马拉车)算法。
但网上大多数博客都晦涩难懂,在此用简单的语言解释、记录一下。
java代码实现在文末。
规则:
1、最长回文子串的长度是半径减1,
2、最长回文子串的起始位置是中间位置减去半径再除以2。
流程:
在输入字符串的字符间隙插入
分隔符“#”(也可为其他符号,不要与字符串中字符相同即可)。目的是使长度为奇数、偶数个数的回文字串,统统变为奇数个数的回文字串。插入分隔符后,在新字串的头尾各加上一个不相等的标识符,作为回文判断边界的
结束标志。(我是
java实现的,所以头尾各加了一个“
$”和“
%”;
c语言实现只需头部加标识符即可,末尾会自带“
\0”作为标识符)。遍历新字串的字符
数组S,以
当前字符作为
回文字符串的中心时,将可达到的
回文字串最大半径存到
int数组P中(回文字串半径从1开始)。
计算P[i]的公式为:P[i]=mx>i?Math.min(P[2*id-i],mx-i):1;
解释一下变量:i是在遍历S数组时的当前索引,id为已知的回文字串的的中间位置(索引),mx=id+P[id],是id对应的回文字串的右边界(索引)。令j=2*id-I,可知j和i是关于id对称的两个点(j+i=2*id)。
解释一下公式,存在三种情况:
当
mx>i,且mx-i>P[j]时。
i处于以
id为中心的回文字串的边界之内,则必然
P[i]=P[j]。
当
mx>i,且mx-i<=P[j]时。由于
P[j]的长度,超出了
i到
mx的距离,至少可知,在
P[i]=mx-i时,即在
i到
mx的距离半径之内,以
i为中心是必然为回文字串的。
当
mx<=i时,至少可知,以
i为中心的字符本身,可作为一个回文字串,
回文半径为1,所以令
P[i]=1。
给P[i]赋值结束之后,使以i为中心的回文字串,继续向左向右进行扩展匹配,如左右边界的值相等,则P[i]++。
4.获得数组P中的最大值(回文字串半径最大值),通过规则获得该最大半径对应的回文字串在输入字串中的起始位置和长度。
5.返回输入字串中,最长回文子字符串的内容。
举例:
S为插入分隔符“#”后的字符串数组。
P[i]对应的是以S[i]字符为回文字符串的中心时,此时的最大回文字符串的半径。
数组P中,值最大的分别为P[5]=4 ,P[7]=4。
由两条规则可推导得:
P[5]=4的中间位置是5,半径是4。则在输入字串中的对应起始位置是0,长度是3,内容为bab。
P[7]=4的中间位置是7,半径是4。则在输入字串中的对应起始位置是1,长度是3,内容为aba。
java代码如下:
public String longestPalindrome(String s) {
//输入为空时的特殊判断
if (null==s){
return "";
}
if (s.length()==0){
return "";
}
//插入#字符,构造S字符数组
StringBuilder builder=new StringBuilder();
builder.append("$#");
for (char c:s.toCharArray()){
builder.append(c);
builder.append("#");
}
builder.append("%");
char[] S=builder.toString().toCharArray();
//构造与S字串相同大小size的P字符数组
int[] P=new int[S.length];
//id为当前回文字串的中心(下标/index),max为id对应的回文字串的右边边界(下标/index)
int max = 0, id = 0;
//用来获取,最大回文半径时,回文字串的中间值下标和半径
int maxCenterIndex=0,maxRadiuslenth=0;
for (int i=1;i<S.length-1;i++){
P[i]=max>i?Math.min(P[2*id-i],max-i):1;
//向左向右扩展匹配,如果两边字符相同则为回文字串,且P[i]回文半径+1
while (S[i-P[i]]==S[i+P[i]]){
P[i]++;
}
//max回文边界扩大; id值跟着i遍历到下一个
if (i+P[i]>max){
max=i+P[i];
id=i;
}
if (maxRadiuslenth<P[i]){
maxRadiuslenth=P[i];
maxCenterIndex=i;
}
}
return s.toString().substring((maxCenterIndex-maxRadiuslenth)/2,(maxCenterIndex-maxRadiuslenth)/2+maxRadiuslenth-1);
}