一文搞懂马拉车算法附算法Java版本实现及实战leetcode题目

算法背景

1975年,Manacher发明了Manacher算法(中文名:马拉车算法),是一个可以在O(n)的复杂度中返回字符串s中最长回文子串长度的算法,十分巧妙。

算法规律

        Manacher发现将字符串每隔一个间隔放置'#',可以简化回文字符串的求解,如:

a    b    b    a    字符串
添加 '#'
    #    a    #    b    #    b    #    a    #
下标:
    0    1    2    3    4    5    6    7    8
以i下标为中心的最长回文子串的半径长度: - p[i]
    1    2    1    2    5    2    1    2    1
规律发现:
(i-p[i])/2 就是子回文串在原字符串的起始位置,但是这里有一个问题就是存在-1
p[i] - 1 就是子回文串在原回文串中的长度
接上述问题解决:
在扩展串中再添加'$'、'@'
如下:
    $    #    a    #    b    #    b    #    a    #    @
i:
    0    1    2    3    4    5    6    7    8    9    10
p[i]:
         1    2    1    2    5    2    1    2    1
再看上述规律是不是符合了呢!

算法高效原因?

不断利用之前的一个回文子串信息,包括了到达的最远边界mx,以及最远边界回文子串的中心坐标id,如果可用,使用,不可以使用就从1开始中心扩展。

将算法从中间拆解如下,相信理解起来会很容易:

 代码实现

647. 回文子串 - 力扣(LeetCode)

class Solution {
    public int countSubstrings(String s) {
        // 马拉车算法
        StringBuilder sb = new StringBuilder("$");
        for(int i=0;i<s.length();++i){
            sb.append("#").append(s.charAt(i));
        }
        sb.append("#@");
        int mx = 0,id=0;
        int res = 0;
        // 定义数组
        int[] p = new int[sb.length()];
        for(int i=1;i<sb.length()-1;++i){
            p[i] = mx>i?Math.min(p[2*id-i],mx-i):1;
            while(sb.charAt(i+p[i])==sb.charAt(i-p[i])){
                p[i]++;
            }
            if(i+p[i]>mx){
                id = i;
                mx = id+p[i];
            }
            res += p[i]/2;
        }
        return res;
    }
}

额外说一下这里的res += p[i]/2,可以将回文串想象成一颗洋葱,再看一下我写的过程图:

有序列表序号对应上图的下标i,第i个回文子串

  1. '#' 半径1,包含原串长度0,贡献度0
  2. 回文串 "#A#"半径2,包含的原串长度1,贡献度1
  3. '#' 半径1,包含原串长度0,贡献度0
  4. 回文串 "#B#"半径2,包含的原串长度1,贡献度1
  5. "#A#B#B#A#"半径5,包含的原串长度4,贡献度2
  6. "#B#"半径2,包含的原串长度1,贡献度1
  7. '#' 半径1,包含原串长度0,贡献度0
  8. "#A#"半径2,包含的原串长度1,贡献度1
  9. '#' 半径1,包含原串长度0,贡献度0

 找一下规律即可得出上述结论公式。

5. 最长回文子串 - 力扣(LeetCode)

class Solution {
    public String longestPalindrome(String s) {
        // 马拉车算法
        StringBuilder sb = new StringBuilder("$");
        for(int i=0;i<s.length();++i){
            sb.append("#").append(s.charAt(i));
        }
        sb.append("#@");
        // 定义数组
        int[] p = new int[sb.length()];
        // mx , id
        int mx=0,id=0,maxLen=0,maxStart=0;
        for(int i=1;i<sb.length()-1;++i){
            p[i] = mx>i?Math.min(p[2*id-i],mx-i):1;
            while(sb.charAt(i+p[i])==sb.charAt(i-p[i])){
                p[i]++;
            }
            if(i+p[i]>mx){
                mx = i+p[i];
                id = i;
            }
            if(p[i]-1>maxLen){
                maxStart = (i-p[i])/2;
                maxLen = p[i]-1;
            }
        }
        return s.substring(maxStart,maxStart+maxLen);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值