思想:Manacher算法
该算法避免了我们在遍历过程中对回文字符串是奇数长度还是偶数长度分别都要进行判断的操作,做法就是在开始结束以及每个字符之间添加特殊字符,以1221为例,进行填充之后就变成#1#2#2#1#,以121为例,进行填充之后就变成#1#2#1#,这样做的好处是,填充之后字符串的长度都成了奇数,这样我们在遍历的过程中对于每一个位置的判断都转换成了同样的操作,并不需要关心最后我们得到的回文串到底是奇数长度还是偶数长度了;
实现:
要想实现的话,这里用到一个辅助数组和两个辅助变量:
radius[ ]:辅助数组,表示每一位置用来表示该位置上的回文半径值
bound:辅助变量,表示记录回文半径即将到达的位置
index:辅助变量,表示bound发生更新的时候,对应bound的回文中心的位置
在我们想要计算第 i 位置上的最大回文串长度时,有两种情况需要考虑:
(1) i 正好在bound当前回文串边界内部,这时候就是Manacher算法的精髓了,见下图,分三种情况来考虑,其中 i' 是 i 相对于index的对称点
为了编程方便,我们会把这三种情况中 i 位置应该开始遍历的初始位置计算成为是上图三种情况中的最小值;
(2)第二种情况的话,i 位于bound边界之外,那么这时候Manacher算法是没法起作用的,因为此时将要遍历的地方之前是没有遍历过的,只能乖乖从当前位置一步一步遍历了,对应于下图这种情况:
程序代码:
/**
* 采用Manacher算法实现求最长回文子串
* radius数组用来存储每个位置上可以向左或者向右最大扩充的半径
* bound表示回文半径即将到达的位置(是下一个位置)
* index表示bound更新的时候对应的回文中心的位置
* @author 扇扇来驰
*
*/
public class GetLongestPalindrome {
public static void main(String[] args) {
String str = "ccbcabaabba";
System.out.println(getLongestPalidrome(str));
}
public static int getLongestPalidrome(String str1)
{
if(str1 == null)
return -1;
if(str1.length() == 0)
return 0;
String str = changeString(str1);
int[] radius = new int[str.length()];
radius[0] = 1;
int bound = 1;
int index = 0;
for(int i = 1;i < str.length();i++)
{
if(bound > i)
{
radius[i] = (bound-i) > (radius[2 * index - i]) ? (radius[2 * index - i]):(bound-i);
}else
radius[i] = 1;
for(int j = 0;i+radius[i]+j < str.length() && i-radius[i]-j >= 0;)
{
if(str.charAt(i+radius[i]+j) == str.charAt(i-radius[i]-j))
radius[i]++;
else
break;
}
if(i+radius[i] > bound)
{
index = i;
bound = i+radius[i];
}
}
return getMax(radius)-1;
}
/**
* 在字符串中添加特殊字符
* @param str
* @return
*/
public static String changeString(String str)
{
String result = "";
for(int i = 0;i < str.length();i++)
{
result += "#"+str.charAt(i);
}
result = result+"#";
return result;
}
public static int getMax(int[] arr)
{
int max = arr[0];
for(int i = 1;i < arr.length;i++)
{
if(arr[i] > max)
max = arr[i];
}
return max;
}
}