数据结构 -- 马拉车算法

查找一个字符串的最长回文子串的线性算法。时间复杂度为O(n)
1. 原理
在原字符串的每个相邻两个字符中间插入一个分隔符,同时在首尾也要添加一个分隔符,分隔符不能在原串中出现,一般情况下可以用#号,使字符串长度变成奇数个。

(1)Len数组性质
用一个辅助数组Len[i]表示以字符T[i]为中心的最长回文字串半径长度
在这里插入图片描述
:Len[i]-1就是该回文子串在原字符串S中的长度,证明:以T[i]为中心的最长回文字串长度是2*Len[i]-1 ,其中分隔符的数量一定比其他字符的数量多1,即有Len[i]-1个字符来自原字符串。

(2)最右回文右边界R
这个位置及之前的位置的回文子串,所到达的最右边的地方。

(3)、最右回文右边界的对称中心C
最右回文右边界的中心点C,如下图,p=4时,R=6,C=3,最早的对应R的中心点
在这里插入图片描述
2. 算法流程

  • 第一种情况:下一个要移动的位置p1在最右回文右边界R的右边。
    将移动的位置为对称中心,向两边扩,同时更新回文半径数组,R和对称中心C。
  • 第二种情况:下一个要移动的位置p1就是R或是在R的左边
    在这种情况下又分为三种:
    p2是p1以C为对称中心的对称点;
    pL是以p2为对称中心的回文子串的左边界;
    cL是以C为对称中心的回文子串的左边界。
    • cL < pL:p1的回文半径就是p2的回文半径radius[p2]。
      在这里插入图片描述
    • cL > pL:p1的回文半径就是p1到R的距离R-p1+1。
      在这里插入图片描述
    • cL = pL:p1的回文半径继续往外扩,但只需从R之后往外扩就可,扩了之后更新R和C。
      在这里插入图片描述

3. 代码实现

public int manacher(String s) {
        if(s == null || s.length() == 0) return 0;

        char[] charArr = manacherString(s);
        int[] radius = new int[charArr.length];
        int R = -1;
        int c = -1;
        int max = Integer.MIN_VALUE;
        for (int i = 0; i < radius.length; i++) {
            radius[i] = R > i ? Math.min(radius[2 * c - i], R - i + 1) : 1;
            while(i+radius[i] < charArr.length && i - radius[i] > -1){
                if(charArr[i - radius[i]] == charArr[i + radius[i]])
                    radius[i]++;
                else
                    break;
            }

            if(i + radius[i] > R){
                R = i + radius[i] - 1;
                c = i;
            }
 			 max = Math.max(max, radius[i]);
        }
 		 return max - 1;
    }

    public static char[] manacherString(String str){
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < str.length(); i++) {
            sb.append("#");
            sb.append(str.charAt(i));
        }
        sb.append("#");
        return sb.toString().toCharArray();
    }

4. 例题
描述:一个字符串只能向后面添加字符,让整个字符串变成回文串,求添加最短字符
解析:求包含最后一个字符的最长回文串,前面不是的部分,逆序过来,添加到字符串尾部即可,即当R到达最后一个字符,由C可得到最左边界,最左边界之前的部分逆序。

public int longestPalindrome(String s) {
        if(s == null || s.length() == 0) return 0;

        char[] charArr = manacherString(s);
        int[] radius = new int[charArr.length];
        int R = -1;
        int c = -1;
        int maxContainsEnd  = -1;
        for (int i = 0; i < radius.length; i++) {
            ...
 
          if(R == redius.length - 1){
               maxContainsEnd = redius[i];
               break;
          }
      }
      char[] res = new char[s.length() - maxContainsEnd + 1];
      for(int i = 0; i < res.length; i++){
           res[res.length - 1 - i] = redius[i * 2 + 1];
      }
      return String.valueOf(res);
 }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值