如何获取字符串中最长回文字子串

   在给定的字符串中获取其中最长的回文字子串,例如:在“kmoomq”中的是”moom“。

   思路1:首先要找到两边一样字符,再两边开始往中间判断,如上例中的”kmoomq“,从”k"开始,到”m"找到另一个"m",再判断中间是否符合。(不推荐,时间复杂度高)

  思路二:由中间对称点(遍历字符串)向两边判断,找到半径最长的,记录中心点和半径,以便可以截取最长回文子串。

      方法一:马拉力(Manacher‘s Algorithm)算法

           首先,对原始字符串(称为s)进行处理,即在开头插入”$"中间插入“#”结尾插入"@"。例如将上例处理为“$#k#m#o#o#m#q#@"(称为str),好处在于将字符串长度统一变为奇数,以及为下面创造一个与str等长的int型一维数组p[](p[i]等于以字符串str中i为中心点的回文字半径长度)提供两个关系式。

           (1) length = p[i] - 1       length:是字符串str下标为i的字符在字符串s中表示的回文字的长度。

           (2)radius = (i-p[i])/2      radius:是字符串str下标为i的字符在字符串s中表示的回文字的半径。

       

public class MyTest1 {

    public static String Manacher(String s){
       //空字符
      if(s==null||s.length==0){
          return "";
      }
      //处理传入字符串
      String str = "$#";
      for(int i=0;i<s.length;i++){
          str = s+"#";
      }
      str +="@";

      //定义辅助记录变量

      //记录最值
      int length = -1;
      int radius = 0;
      //id,mx 都是相对于p[]而言,且是当前而言的最值,可变
      int id = 0;     //当前最长回文字中心点下标
      int mx = 0;    //该下标所能到达的最右边界 
    
      //创建等长数组p[]
      int n = str.length;
      int[] p = int[n];

      //循环从第二个(p[1])倒数第一个(p[n-2])
      for(int i=1;i<n-1;i++){
         //看能不能走捷径
         p[i] = mx > i ? Math.min(p[2*id-i],mx-i):1;
         //这就是判断str字符串第i个位置以半径为p[i]的值进行逐渐加一增长比较
         while(str.charAt(i+p[i])==str.charAt(i-P[i])){
               p[i]++;
         }
         //更新mx和id的值
         if(p[i]+i>mx){
            mx = p[i]+i;
            id = i;
         }
         if(length<p[i]){
            lenght = p[i];
            index = i;
         }
      }
      index = (index - length)/2; //(2)
      length = length - 1;      //(1)
      
      return s.substring(index,(index+length));

    }
}

  在代码中的  p[i] = mx > i? Math.min(mx-i,p[2*id-i]):1  比较经典难以理解,其实就是去找找捷径,让p[i]的值不用从1开始加起。

       这是个三目运算符,就是给p[i]赋值,

       第一种情况:

              如果mx > i,假设j = p[2*id-i],就让p[i] 等于(mx - i)和j中更小的值。先来看看(mx - i)和j比较有啥意义。

               mx是在本次i之前的回文字子串所能到达的最右值 ,其中心点为id。那么在mx关于id的对称点到mx这个范围内有回文字的特性,即i关于id对称的点j(2*id=i+j  =>  j= 2*id - i)有p[i] = p[j] 的关系。

               当p[j]要小于mx - i时,即i+p[j]<mx 可以让p[i]的初始值赋值为p[j]。                          

               当p[j]要不小于mx - i时,因为有回文字的特性只在mx关于id的对称点到mx这个范围内。所以p[i]初始值赋值为mx-i

       第二种情况:

             如果mx < i,因为有回文字的特性只在mx关于id的对称点到mx这个范围内,所以只能乖乖给p[i]赋值为1,再开始判断是否累加。

    该算法时间复杂度度为线性。

   方法二:方法一的优化算法

       有了方法一的解释这个理解起来并不难,相比起来优点在于不用预先处理字符串。

public class MyTest2 {
    public static String Manacher(String s) {
        if(s==null||s.length()==0){
            return "";
        }
        int[] range = new int[2];
        char[] mychar = s.toCharArray();
        for (int i = 0; i < mychar.length; i++) {
            i = find(range,i,mychar);
        }
    return s.substring(range[0],range[1]+1);
    }
    public static int find(int[] range,int left,char[] mychar){
        int max = mychar.length-1;
        int right = left;

        while (right<max&&mychar[left]==mychar[right+1]){
            right++;
        }

        int result = right;

        while (left>0&&right<max&&mychar[left-1]==mychar[right+1]){
            left--;
            right++;
        }
        if(range[1]-range[0]<right-left){
            range[0] = left;
            range[1] = right;
        }
        return result;
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值