前天听到了Manacher’s Algorithm算法,感觉很神奇,特此分享
Manacher’s Algorithm(原版本讲解链接:https://www.geeksforgeeks.org/manachers-algorithm-linear-time-longest-palindromic-substring-part-1/)
解决回文字符串问题有三种方法:
- Brute Force 取出来所有的可能子串然后暴力遍历。
int[] pos={0,0};
for(int i=0;i<s.length;i++){
for(int j=0;j<s.length;j++){
//find every substring then judge them one by one
if(isPalindromic(s,i,j)){
if(pos[1]-pos[0]<(j-i)){
pos[1]=j;
pos[0]=i;
};
};
}
}
2.Dynamic Programming 动态规划---记忆化存储---表格表示
begin:在这个表格里面去除end< start的空格
然后填入length=0->length=1->length =2 ->length =......
递推关系如下if(0对则1对则2对则。。。。。)若任何中间一个是false我们就可以把这一列cut掉,最多我们只计算(n2)/2次
结果如下:
具体代码会很容易写出来:
class GFG {
public static void solve(String s){
int[][] dp = new int[s.length()][s.length()];
int maxlength=0;
int start=0;
for(int i=0;i<s.length();i++){
dp[i][i]=1;
}
for(int len=1;len<s.length();len++){
for(int i=0;i<s.length()-len;i++){
if((len==1||dp[i+1][i+len-1]==1)&&s.charAt(i)==s.charAt(i+len)){
dp[i][i+len]=1;
if(maxlength<len){
maxlength=len;
start=i;
}
}
}
}
System.out.println(s.substring(start,start+maxlength+1));
}
public static void main (String[] args) {
//code
Scanner sc =new Scanner(System.in);
int T=sc.nextInt();
for(int i=0;i<T;i++){
String s=sc.next();
solve(s);
}
}
}
由图得 时间和空间复杂度都是O(n2)
3. Manacher’s Algorithm(马拉车算法)
想一下,回文是从中心扩展的,那么如果我们知道中心,则会很容易的判断是否是回文。
处理一: 在每个字母中间插入一个相同的字符,取消的奇偶分类。使得中心扩展判断成为可能。
b # a # b # c # b # a # b # c # b # a # c # c # b # a
处理二: 回文字符串的对称性: 对称区域内的子回文长度对称相等(if len<R)
b # a # b # c # b # a # b # c # b # a #c # c # b # a
根据对称性可以直接求出来大回文右半部分的数值,减少很多计算
处理三: 如果子回文长度超出对称区域->分类:在区域内的可以保证,区域外的不能保证,需要增加判断。
import java.util.Arrays;
public class Manacher {
public static String isPString(String s) {
//初始化
char[] str = new char[2 * s.length() + 1];
int[] record = new int[str.length];
Arrays.fill(record, 0);
for (int i = 0; i < s.length(); i++) {
str[2 * i] = '#';
str[2 * i + 1] = s.charAt(i);
}
str[2 * s.length()] = '#';
//rp:right point
//lp is center
int lp = 0;
int rp=0;
while (lp < str.length) {
if(rp==lp){
rp++;
}else {
//回文的话rp++
while (2*lp-rp>=0&&rp<str.length&&str[rp]==str[2*lp-rp]){
rp++;
record[lp]++;
}
//lp ~ rp 对称取对称数值:超出重新计算
int mp = lp+1;
while (mp<rp){
record[mp]=record[2*lp-mp]>(mp-lp)?outR(str,mp,mp-lp) :record[2*lp-mp];
mp++;
}
lp=rp;
}
}
int max=0;
int start=0;
for(int i=0;i<record.length;i++){
if(record[i]>max){
start=i>>1;
max=record[i];
}
}
return start+max/2+1<s.length()?s.substring(start-max/2,start+max/2+1):s.substring(start-max/2);
}
public static int outR(char[] str,int pos,int r){
while (pos-r>=0&&pos+r<str.length&&str[pos+r]==str[pos-r]){
r++;
}
return r-1;
}
public static void main(String[] args){
String s= "abababbb";
System.out.println(isPString(s));
}
}
欢迎补充
编程之美的写法,感觉更好
public static void manacher(char[] p){
int n=p.length;
//中心
int id;
//右边界,id+p[id]
int mx=0;
//时刻移动的指针 i<mx p[i]=Math.min(p[2*id-i],mx-i) i>mx p[i]=1
int i;
for(int i=0;i<n;i++){
if(i<mx){
p[i]=Math.min(p[2*id-i],mx-i);
}else p[i]=1;
//如果超出的管辖范围则先改变半径
while(p[i+p[i]]==p[i-p[i]]){
p[i]++;
}
//如果超出的管辖范围则移动指针
if(p[i]+i>=mx){
mx=p[i]+i;
id=i;
}
}
}