dijkstra’s algorithm_回文问题:三图讲懂Manacher’s Algorithm(马拉车算法)

前天听到了Manacher’s Algorithm算法,感觉很神奇,特此分享

Manacher’s Algorithm(原版本讲解链接:https://www.geeksforgeeks.org/manachers-algorithm-linear-time-longest-palindromic-substring-part-1/)

解决回文字符串问题有三种方法:

  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 动态规划---记忆化存储---表格表示

926b0814b22b412e361f69581b858cea.png

begin:在这个表格里面去除end< start的空格

然后填入length=0->length=1->length =2 ->length =......

递推关系如下if(0对则1对则2对则。。。。。)若任何中间一个是false我们就可以把这一列cut掉,最多我们只计算(n2)/2次

d1b05477240ca0218f816ff4e9640bd7.png

结果如下:

e53fdadd71451bc5431eb107415a30dc.png

具体代码会很容易写出来:

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

根据对称性可以直接求出来大回文右半部分的数值,减少很多计算

7dfbff6a672d2f242703f0ee45f4d444.png

处理三: 如果子回文长度超出对称区域->分类:在区域内的可以保证,区域外的不能保证,需要增加判断。

6ad583364cbb0b24e212472c07aba947.png
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;
      }
      }     
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值