回文串在笔试编程题中很常见,昨天在牛客网上听了左神讲解的manacher算法,受益匪浅,下面就是我整理的manacher算法。
manacher算法是用来求一个字符串中的最长回文子串。
求字符串中的最长回文子串有两种方法:
(1)暴力求解法
即从左往右遍历字符串,对每一个点进行扩,求出每一个点的回文子串,找出最长回文子串,时间复杂度为O(n^2)
下面是用Java实现的:
package com.example.test;
import java.util.Scanner;
public class Test5 {
static void LongestPalindromicSubstring(String str){
char[] buf = str.toCharArray();
int len = buf.length;
int max = 0;
String ans = new String() ;
for(int i=0 ; i<len ; ++i){
int j = i-1;
int k = i+1;
int cur = 0;
while( j>=0 && k<len && buf[j]== buf[k]){
cur++;
j--;
k++;
}
cur = cur*2+1;
if(cur > max){
max = cur;
ans = str.substring(j+1,k);
}
}
System.out.println(ans);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner scn = new Scanner(System.in);
String str = scn.nextLine();
LongestPalindromicSubstring(str);
}
}
(2)manacher算法:
从一个中心点向两边扩的时候会有奇回文和偶回文,为了统一解决这两种情况,我们可以给所求字符串的两端,和字符之间加上一个统一的字符,如:字符串abcdcbd,变成了#a#b#c#d#c#b#d#。这样转化后我们所求得的回文数除以2就是原始长度。
manacher算法的三个重点:
①回文半径数组。每一个点往外扩都会有相对的回文半径,将其回文半径存放在一个数组中。
②回文最右边界。每一个点在向外扩时,都会有右边界,如何某个点的右边界比先前的右边界往更右的方向。则我们应该更新最右边界。如下图扩a点时其回文右边界在第一个右括号处,扩b点时其回文右边界在第二个右括号处,扩b点时,最右回文边界被更新。
③回文最右边界中心。与②一致。需要注意的是,当两个或多个点的最右边界相同时,记录最早的那个。
manacher算法:分4种情况 时间复杂度O(n)
①当前点没有在回文最右边界里,扩。
从一个中心点向两边扩的时候会有奇回文和偶回文,为了统一解决这两种情况,我们可以给所求字符串的两端,和字符之间加上一个统一的字符,如:字符串abcdcbd,变成了#a#b#c#d#c#b#d#。这样转化后我们所求得的回文数除以2就是原始长度。
manacher算法的三个重点:
①回文半径数组。每一个点往外扩都会有相对的回文半径,将其回文半径存放在一个数组中。
②回文最右边界。每一个点在向外扩时,都会有右边界,如何某个点的右边界比先前的右边界往更右的方向。则我们应该更新最右边界。如下图扩a点时其回文右边界在第一个右括号处,扩b点时其回文右边界在第二个右括号处,扩b点时,最右回文边界被更新。
③回文最右边界中心。与②一致。需要注意的是,当两个或多个点的最右边界相同时,记录最早的那个。
manacher算法:分4种情况 时间复杂度O(n)
①当前点没有在回文最右边界里,扩。
②当前点i在回文最右边界里,其对称点i'在LR内,不扩,arr[i] = arr[i'];
③当前点i在回文最右边界里,其对称点i'在LR外,不扩,arr[i] = R-i;
④当前点i在回文最右边界里,其对称点i'压线,扩,因为无法确定
Java代码实现:
package com.example.test;
import java.util.Scanner;
public class Test5 {
static void LongestPalindromicSubstring(String str){
StringBuffer buffer = new StringBuffer();
buffer.append("#");
char[] buf = str.toCharArray();
for(int i=0 ; i<buf.length ; ++i){
buffer.append(buf[i]+"#");
}
buf = buffer.toString().toCharArray();
int len = buf.length;
int[] arr = new int[len];
int rr = -1;
int center = -1;
for(int i=0 ; i<len ; ++i){
int j = i-1;
int k = i+1;
int cur = 0;
if(i > rr){ //没有在回文最右边界里,扩
while( j>=0 && k<len && buf[j] == buf[k]){
cur++;
j--;
k++;
}
arr[i] = cur;
}else{ //在回文右边界里
int ll = center - arr[center];
int lcur = center - (rr-center);
int llcur = lcur-arr[lcur];
if( llcur > ll){ //当前节点的对称点的范围在LR中,不扩
arr[i] = arr[lcur];
}else if( llcur < ll){ //当前节点的对称点的范围在LR外,不扩
arr[i] = rr-i;
}else{
while( j>=0 && k<len && buf[j] == buf[k]){ //当前节点的对称点的左范围和L重叠,扩
cur++;
j--;
k++;
}
arr[i] = cur;
}
}
if( i+cur > rr){
rr = i +cur;
center = i;
}
}
int max = 0;
for(int i=0;i<len;++i){
if(arr[i]>max){
max = i;
}
}
System.out.println(str.substring((max-arr[max])/2, (max+arr[max])/2));
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner scn = new Scanner(System.in);
String str = scn.nextLine();
LongestPalindromicSubstring(str);
}
}
上面的图画的有点丑,应该可以看懂