Manacher算法

本文详细介绍了Manacher算法如何在O(n)时间复杂度内找到字符串的所有回文子串,并结合LeetCode题目实践,包括最长回文子串和最大奇数回文子串乘积。通过预处理、动态规划及对称性利用,深入剖析算法原理并提供代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

思路:

Manacher算法在O(n)时间复杂度求所有回文子串。

思路为:当求后面某一位为中心时的回文子串时,对前面的回文字串信息的复用。类似KMP。本质还是动态规划。

核心思想是:当前点i为中心的对称长度 基于上一层中心点mid与i的对称点p。

维护中心点mid和最远的r,当[mid,i]中的点和[p,mid]中的点对称,利用对称性,基于以p为中心的部分点,能预先求出以i为中心的部分点,即预先求出以i为中右边的延申点为min(r-i,dp[p])

考虑到回文子串长度还有奇数或偶数,需要对原字符串S进行预处理,插值为另一个字符串T(插值方式见网上资料)。当T中以原字符串的字符为中心时对应奇数子串,以插入的字符“#”为中心时对应偶数子串。之后在将T中包含的回文信息,转换回S的回文信息(从起始字符的对应关系和长度的对应关系,转换到原字符串的回文子串)。

参考:有什么浅显易懂的Manacher Algorithm讲解? - 知乎

题1:leetcode5. Longest Palindromic Substring

Loading...

模板题:

class Solution {
    public String longestPalindrome(String s) {
        int n=s.length();
        int m=2*n+3;
        char[] chs=new char[m];
        for(int i=0,j=2;i<n;i++,j+=2){
            chs[j]=s.charAt(i);
        }
        for(int j=1;j<m;j+=2){
            chs[j]='#';
        }
        chs[0]='^';
        chs[m-1]='$';
        //dp记录了插值后的回文子串半边的长度,等于原字符串回文子串的总长度
        int[] dp=new int[m];
        //每次维护一个之前最远的回文子串,求下一个子串时可以利用之前子串的对称性,最大程度复用之前的信息
        int mid=0,r=0;
        for(int i=1;i<m-1;i++){
            int p=2*mid-i;
            if(r>i){
                dp[i]=Math.min(dp[p],r-i);
            }else{
                dp[i]=0;
            }
            while(chs[i-dp[i]-1]==chs[i+dp[i]+1]){
                dp[i]++;
            }
            if(i+dp[i]>r){
                r=i+dp[i];
                mid=i;
            }
        }
        int idx=0,len=0;
        for(int i=0;i<m;i++){
            if(dp[i]>len){
                len=dp[i];
                idx=i;
            }
        }
        //从起始字符的对应关系和长度的对应关系,转换到原字符串的回文子串
        int start=(idx-len)/2;
        int end=start+len;
        return s.substring(start,end);
    }
}

题2:leetcode1960. Maximum Product of the Length of Two Palindromic Substrings

Loading...

因为这里只需要求奇数长度回文子串,所以不用插值。之后用两个数组记录左右两边的最大回文子串,用双指针求。

class Solution {
    public long maxProduct(String s) {
        int n=s.length();
        char[] chs=s.toCharArray();
        int[] dp=new int[n];
        int mid=0,r=0;
        for(int i=1;i<n-1;i++){
            int p=2*mid-i;
            if(r>i){
                dp[i]=Math.min(dp[p],r-i);
            }else{
                dp[i]=0;
            }
            while(i-dp[i]-1>=0&&i+dp[i]+1<n&&chs[i-dp[i]-1]==chs[i+dp[i]+1]){
                dp[i]++;
            }
            if(i+dp[i]>r){
                r=i+dp[i];
                mid=i;
            }
        }
        long[] L=new long[n];
        long[] R=new long[n];
        for(int i=0,j=0;j<n;j++){
            if(j>0){
                L[j]=L[j-1];
            }
            while(i+dp[i]<j){
                i++;
            }
            L[j]=Math.max(L[j],(j-i)*2+1);
        }
        for(int i=n-1,j=n-1;j>=0;j--){
            if(j<n-1){
                R[j]=R[j+1];
            }
            while(i-dp[i]>j){
                i--;
            }
            R[j]=Math.max(R[j],(i-j)*2+1);
        }
        long ret=0;
        for(int i=0;i<n-1;i++){
            ret=Math.max(ret,L[i]*R[i+1]);
        }
        return ret;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值