算法与数据结构——Manacher(Java)(b站左程云课程笔记总结)

Manacher

解决问题:

字符串str中,最长回文子串的长度如何求解

要求:时间复杂度为O(N)

一开始思路(经典解法):每一个字符从中心开始扩散对比左右并记录长度

有问题:长度为偶数的回文没办法得到(轴是虚的),只有奇数的回文能得到

解决方法:在每两个字符之间加一个字符#(不一定要求原字符串中没出现过的字符)(实的跟实的比,虚的跟虚的比,不会出现实的和虚的比)

这样可以得到答案,最终答案需要/2

时间复杂度:O(N^2):最差情况:全部字符都相等

Manacher(时间复杂度:O(N)思路与经典解法类似,但是在过程中有优化(与kmp类似)

几个概念:

  • 回文直径:从中心往左右两边扩出来的整个区域的大小
  • 回文半径:回文直径/2
  • 之前所扩的位置中所到达的最右回文右边界R:该数初始值为-1,不管是哪个位置扩的,只要扩的范围的右边界比当前数大,就替换
  • 变量C:跟R同步更新,初始值为-1,取得最右回文右边界时中心的位置

第一种情况:

当前点的位置不在最右回文右边界R的范围(包括当前点刚好与R重合),暴力扩(跟经典做法一样)(没有优化)

第二种情况:
当前点的位置在最右回文右边界R的范围里,此时C一定在i的左侧,i可能与R重合,根据R与C可以做出L(回文半径),根据C与i可以做出i’

image-20220622232231812

根据i位置的回文状况对第二大类情况进行分类:

  1. i‘位置的回文区域整个都在L到R区域的内部,此时的i位置的回文区域大小跟i’一样,如图,此时乙区域是甲区域的逆序(L到R为回文),甲是回文串,至此i位置的回文长度至少是i’的回文长度,再根据图中的三个条件判断得到i位置的回文长度与i’位置的相等

image-20220622233217595

  1. i’的回文区域一部分在L到R外面,此时i的回文长度为i到R,证明过程如图

image-20220622234925233

  1. i‘的回文区域的左边界与L重合,i的回文长度至少等于i’的回文长度,可能会变得更大,要看左右部分(小加速)

image-20220622235321807

时间复杂度的证明根据i和R两个变量来估计分析,四个循环分别分析,i和R的最大值都是N,每个分支结束i都会++,R只有在暴力扩的时候才有可能增加(即i和R都是只增不退的)四个分支都在for循环里,每次只会执行一个,第一个分支和第四个分支可能会扩失败一次,第二和第三个分支不会扩,即每个位置最多只会扩失败一次,所以扩失败的代价是O(N),而R变大的总幅度就是扩成功的次数(R如果等于字符串的总长度N循环就可以结束了?),即扩成功的代价是O(N)(扩成功R至少+1),另外两个分支时间复杂度是O(1),因此整个过程时间复杂度是O(N)

image-20220623000854773

public int maxLcpsLength(String s){
    if(s==null||s.length()==0){
        return 0;
    }
    char[] str=manacherString(s);//加特殊字符
    int[] pArr=new int[srt.length];//回文半径数组
    int C=-1;//中心位置
    int R=-1;//注意 这里为了coding,R代表的是回文右边界的再往右一个位置,最右的有效区是R-1位置
    int max=Integer.MIN_VALUE;//扩出来的最大值
    for(int i=0;i!=str.length;i++){//注意这里的终止条件
        pArr[i]=R>i?Math.min(pArr[2*C-i],R-i):1;//i至少的回文半径(至少不用验证的区域),先给pArr[i]  2*C-i就是i'
        while(i+pArr[i]<str.length&&i-pArr[i]>-1){//四种情况都看看能不能往外扩,第二第三分支不用往外扩,就算扩一次也会失败,增加的是一点点常数时间(不会改变时间复杂度),带来的是if-else的减少(代码简洁性),追求极致性能也可以增加
            if(str[i+pArr[i]]==str[i-pArr[i]]){
                pArr[i]++;
            }else{
                break;
            }
        }
        if(i+pArr[i]>R){//更新R和i
            R=i+pArr[i];
            C=i;
        }
        max=Math.max(max,pArr[i]);//g
    }
    return max-1;//半径-1即真正的回文直径(有特殊字符)   
}

public char[] manacherString(String str){
    char[] charArr=str.toCharArray();
    char[] res=new char[str.length*2+1];
    int index=0;
    for(int i=0;i!=res.length;i++){
        res[i]=(i&1)==0?'#':charArr[index++];
    }
    return res;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值