Manacher算法解决最长回文串问题
如果求一个字符串,求字符串中最长连续回文子串问题,如果用暴力求解的话会使得时间复杂度为O(n^2),因为暴力方法在以不同字符为中心往两边扩的时候,每次都是往两边扩,那么其中有好多重复扩的部分。而本文讲解的manacher算法就是使得前面将前面扩的部分备份,后面扩的时候便于省去不需要重复扩的部分,从而降低时间复杂度,使得时间复杂度大约为O(n)。首先先说明manacher算法的三个重要的变量,pArr数组,用于存储以每一个位置为中心的最大回文半径;right表示右边界,即当前位置之前扩到字符的最右边界;index表示扩到right时对应的字符位置。在进行算法之前,需要对算法进行预处理,为了避免奇偶性,所以在该字符串中穿插一个标记字符,比如'#',在字符串首尾以及每两个字符之间插入一个标记字符。
然后进行讨论manacher的算法思路:
1.如果当前位置i在right之外,则不断往两边扩,扩到不是回文为止。
2.如果i在right之内,则需要讨论几种情况:这时候需要找到i关于index的对称位置:2*index-i,并判断pArr[2*index-i]与right-i的关系,如果前者小于后者,那么pArr[i]=pArr[2*index-i],并且不需要再往外扩,只需要接着判断下一个i即可;还有种情况便是pArr[2*index-i]大于等于right-i,这时候需要截取的是right-i的部分,因为right之后的字符是未知的,所以需要以i为中心,并从半径right-i开始往两边扩,所有情况扩的过程不仅要满足字符对称相等,还需要满足边界问题。假设扩的半径为r,则需要满足i-r>=0同时满足i+r<length.我们首先来分析一下比较复杂的情况,就是i关于index对称位置的左边界在index为中心的左边界的左边的情况,如下图所示:
如上图所示的字符串的当前index和right如图所示,假设目前的i位置如图所示,该位置的对称位置i'如图所示,图中可知i'的左边界超出了index的左边界,因此不能直接把i'的回文半径给i位置使用,因为i位置的right之后是位置的。但是可以保证的是2*i-right-->i-->right部分是回文串,根据这段关于index对称到左边对应的是i'为中心的最大回文串的子回文串,所以可以将开始扩的位置定位到right+1开始。所以可以看出,当i<right的时候,可以总结为在pArr[2*index-i]和pArr[index]+index-i之间取较小值即可。假设i位置的半径最终为r,如果i+r-1>right的时候需要更新right的index值。注意r是局部变量,每遍历一个i值时,r都从1开始。
具体代码如下所示:
import java.util.*;
public class Manacher {
public static int manacher(String str)
{
//首先进行预处理。
int n=str.length();
StringBuffer sb=new StringBuffer();
sb.append("#");
for(int i=0;i<n;i++)
{
sb.append(str.charAt(i)).append("#");
}
int[] pArr=new int[2*n+1];
int right=-1;//为了后续更容易判断,从-1开始。
int index=-1;
for(int i=0;i<sb.length();i++)//开始逐个遍历求以各个字符为中心的最大回文半径。
{
int r=1;//每个字符最少半径为1.
if(i<=right)//判断是够在right之内,如果是则寻找开始扩充的边界,如果不是则直接从当前字符往两边扩充。
{
r=Math.min(pArr[2*index-i], pArr[index]+index-i);//需要取小值,因为对称点回文串左边界可能在index左边界之外。对于当前点来说它已知的边界不能超过right。
}
//开始扩充,扩充的时候需要在保证相等的时候注意边界。
while(i-r>=0&&i+r<sb.length()&&sb.charAt(i+r)==sb.charAt(i-r))
r++;
pArr[i]=r;
//扩充完之后需要检查是否需要更新index和right
if(i+r-1>right)
{
right=i+r-1;
index=i;
}
}
StringBuffer sb1=new StringBuffer();
int maxnum=1;
int k=0;
for(int i=0;i<pArr.length;i++)
{
if(pArr[i]>maxnum)
{
maxnum=pArr[i];
k=i;
}
}
System.out.println(k);
for(int i=k-maxnum+1;i<k+maxnum;i++)
{
if(sb.charAt(i)!='#')
sb1.append(sb.charAt(i));
}
System.out.println(sb1.toString());
return sb1.length();
}
public static void main(String[] args) {
// TODO Auto-generated method stub
String str="baabbaaa";
System.out.println(manacher(str));
}
}