在给定的字符串中获取其中最长的回文字子串,例如:在“kmoomq”中的是”moom“。
思路1:首先要找到两边一样字符,再两边开始往中间判断,如上例中的”kmoomq“,从”k"开始,到”m"找到另一个"m",再判断中间是否符合。(不推荐,时间复杂度高)
思路二:由中间对称点(遍历字符串)向两边判断,找到半径最长的,记录中心点和半径,以便可以截取最长回文子串。
方法一:马拉力(Manacher‘s Algorithm)算法
首先,对原始字符串(称为s)进行处理,即在开头插入”$"中间插入“#”结尾插入"@"。例如将上例处理为“$#k#m#o#o#m#q#@"(称为str),好处在于将字符串长度统一变为奇数,以及为下面创造一个与str等长的int型一维数组p[](p[i]等于以字符串str中i为中心点的回文字半径长度)提供两个关系式。
(1) length = p[i] - 1 length:是字符串str下标为i的字符在字符串s中表示的回文字的长度。
(2)radius = (i-p[i])/2 radius:是字符串str下标为i的字符在字符串s中表示的回文字的半径。
public class MyTest1 {
public static String Manacher(String s){
//空字符
if(s==null||s.length==0){
return "";
}
//处理传入字符串
String str = "$#";
for(int i=0;i<s.length;i++){
str = s+"#";
}
str +="@";
//定义辅助记录变量
//记录最值
int length = -1;
int radius = 0;
//id,mx 都是相对于p[]而言,且是当前而言的最值,可变
int id = 0; //当前最长回文字中心点下标
int mx = 0; //该下标所能到达的最右边界
//创建等长数组p[]
int n = str.length;
int[] p = int[n];
//循环从第二个(p[1])倒数第一个(p[n-2])
for(int i=1;i<n-1;i++){
//看能不能走捷径
p[i] = mx > i ? Math.min(p[2*id-i],mx-i):1;
//这就是判断str字符串第i个位置以半径为p[i]的值进行逐渐加一增长比较
while(str.charAt(i+p[i])==str.charAt(i-P[i])){
p[i]++;
}
//更新mx和id的值
if(p[i]+i>mx){
mx = p[i]+i;
id = i;
}
if(length<p[i]){
lenght = p[i];
index = i;
}
}
index = (index - length)/2; //(2)
length = length - 1; //(1)
return s.substring(index,(index+length));
}
}
在代码中的 p[i] = mx > i? Math.min(mx-i,p[2*id-i]):1 比较经典难以理解,其实就是去找找捷径,让p[i]的值不用从1开始加起。
这是个三目运算符,就是给p[i]赋值,
第一种情况:
如果mx > i,假设j = p[2*id-i],就让p[i] 等于(mx - i)和j中更小的值。先来看看(mx - i)和j比较有啥意义。
mx是在本次i之前的回文字子串所能到达的最右值 ,其中心点为id。那么在mx关于id的对称点到mx这个范围内有回文字的特性,即i关于id对称的点j(2*id=i+j => j= 2*id - i)有p[i] = p[j] 的关系。
当p[j]要小于mx - i时,即i+p[j]<mx 可以让p[i]的初始值赋值为p[j]。
当p[j]要不小于mx - i时,因为有回文字的特性只在mx关于id的对称点到mx这个范围内。所以p[i]初始值赋值为mx-i
第二种情况:
如果mx < i,因为有回文字的特性只在mx关于id的对称点到mx这个范围内,所以只能乖乖给p[i]赋值为1,再开始判断是否累加。
该算法时间复杂度度为线性。
方法二:方法一的优化算法
有了方法一的解释这个理解起来并不难,相比起来优点在于不用预先处理字符串。
public class MyTest2 {
public static String Manacher(String s) {
if(s==null||s.length()==0){
return "";
}
int[] range = new int[2];
char[] mychar = s.toCharArray();
for (int i = 0; i < mychar.length; i++) {
i = find(range,i,mychar);
}
return s.substring(range[0],range[1]+1);
}
public static int find(int[] range,int left,char[] mychar){
int max = mychar.length-1;
int right = left;
while (right<max&&mychar[left]==mychar[right+1]){
right++;
}
int result = right;
while (left>0&&right<max&&mychar[left-1]==mychar[right+1]){
left--;
right++;
}
if(range[1]-range[0]<right-left){
range[0] = left;
range[1] = right;
}
return result;
}
}