记得刚开始学习计算机的时候,除了输出星星就是找到最长回文字符串这样的问题, 开始以为最长回文串问题很简单,但是经过多年的学习再回头看的时候发现,它并不简单,今天就给它解个密。
开始我们先来看一个时间复杂度差的算法。
1. 时间复杂度为 n^3. 空间复杂度为 1. 这个算法基本上是最简单的了,最好理解的。
/**
* 使用iteration
* 循环三次
* 第一次(最外边循环 : 从最长的长度开始,循环长度,每次减1
* 第二次 (中间循环): 对每个长度,循环最长字符串的起始点,起点从0每次加1
* 第三次 (最内循环): 对每个长度每个起点,对左右进行比较,每次左+1,右-1
*
*
*
*
* */
public static String longestP (String S) {
if (S.length() < 2 || S == null) { return S; }
int length = S.length();
int left = 0;
int right = 0;
while (length >= 0) {
for (int i = 0; i + length - 1 < S.length(); i++) {
left = i;
right = i + length - 1;
while (left < right) {
if (S.charAt(left) == S.charAt(right)) {
left ++;
right --;
continue;
} else {
break;
}
}
if (left >= right) { return S.substring(i, i+ length); }
}
length --;
}
return "";
}
这个算法需要一个预处理和主要的三个变量。
预处理: 将输入字符串的各个字符使用特殊字符进行 分割,比如 #。 e.g. input: abba 处理: #a#b#b#a#
三个主要变量:
1. 一个和预处理之后大小一样的 int 数组 rad[] - 用来存储以当前字符为中心的最长子回文字符串的半径。
2. 一个整形变量来存储- 我们遍历过的所有子回文字符串能触及到的最右边的位置的maxRight。
3. 另一个整形变量来存储 - 2.中maxRight所对应的 子回文字符串的中心点的位置pos。
每次遍历从以当前字符为中心向两头扩展,扩展的半径是多少?
这就是个问题。。。
解决办法, 通过对比 i 和 maxRight, 当i < maxRight 的时候, 判断 maxRight - i 的大小 和 一个点 j ( 这个点是 i 以 pos为对称的 对称点 )的最长回文半径的大小(存储在rad 中).
使用较小值作为半径, 从当前点开始进行两头扩张。每次扩张成功之后半径+1。
public static String Manacher (String S) {
if (S.length() < 2 || S == null) { return S; }
//construct new string
//basically, insert one # to the String S,
//make sure each character is surrounded by #
StringBuilder newS = new StringBuilder();
newS.append("#");
int start = 0;
while (start < S.length()) {
newS.append(S.charAt(start));
newS.append("#");
start ++;
}
int [] rad = new int[newS.length()]; //to store the radius of a string with pivot of current node
int maxRight = -1; //most right we can touch.
int pos = -1; //the position for which node that can touch the most right.
for (int i = 0; i < newS.length(); i++) {
int r = 1; //radius
if (i <= maxRight) {
//since i is less than the max right,
//so we can compare the radius of the node which is symmetric with pos and centered on current node
r = Math.min(rad[2*pos - i], maxRight - i);
}
//extend the string, compare with the i - r(left) and i + r (right)
//if they equal, so that radius ++
while (i - r >= 0 && i + r < newS.length()
&& newS.charAt(i-r) == newS.charAt(i+r)) {
r++;
}
//if i + r -1 is greater than the most right that the most right we can touch.
//we have to update the max right and the position of max right
if (i + r - 1 > maxRight) {
maxRight = i + r - 1;
pos = i;
}
rad[i] = r;
}
int MaxR = 0;
int pivot = 0;
for (int i = 0; i < rad.length; i++) {
if (rad[i] > MaxR) {
MaxR = rad[i];
pivot = i;
}
}
return newS.substring(pivot - MaxR + 1, pivot + MaxR).replace("#", "");
}