Manacher和KMP总结

Manacher算法

用于寻找回文子串的加速算法,一般情况来说,我们要判断一个字符串中的最长回文子串我们就以每一个字符为中心,进行扩散来判断,这样的时间复杂度是O(N^2)的,而Manacher算法能够实现O(N)的时间复杂度,具有很好的性能。

算法的流程:

1. 首先我们对字符串进行填充改写,比如一个字符串"aba",我们将其填充为"#a#b#a#",这样做的目的是为了在回文子串确定过程中对于奇偶性的讨论,什么意思呢?

比如一个字符串为abba,那么我们以任何一个字符为中心进行扩散都不会判定为回文串,但这样的字符串确实是一个回文序列,为了避免这种情况的讨论,我们使用#进行填充,#a#b#b#a#,以最中心的#进行扩充就可以得到我们最长的回文序列,长度为9,这个时候我们只需要将得到的长度除2即可得到我们本身所需求的长度。

2. 使用到的概念有,R-目前找到的最长回文右边界,C-目前最长回文右边界对应的回文中心位置,以及一个回文半径数组,代表了不同位置的回文半径,比如#a#b#a#,回文半径数组为[1,2,3,1,2,1]。

3. 从左到右对字符串进行遍历,遍历位置为i,如果i>=R,那么进行暴力扩散计算,如果i<R,那么去看i关于C对称的点i‘,如果i'的回文序列在L内,(L为R关于C对称的点),那么此时i对应的回文半径为i’对应的回文半径,如果回文序列在L外,此时i对应的回文半径为R-i,如果回文序列在L上,那么初始回文半径为R-i,进行扩散计算。注意,在每次计算中都会得到对应的R和C,C=i,R=i+回文半径,如果R变得更大了那么R和C一起更新,否则不更新。

5. 最长回文子串 - 力扣(LeetCode)icon-default.png?t=M85Bhttps://leetcode.cn/problems/longest-palindromic-substring/

// manacher
class Solution {
public:
    string longestPalindrome(string s) {
        string str;
        for(int  i = 0 , idx = 0; i < s.size()*2+1 ; i++){
            str+=i%2==0?'#':s[idx++];
        }
        int R = -1;
        int C = -1;
        int pref[str.size()];
        for(int i = 0 ; i<str.size() ; i++){
            pref[i] = R>i?min(pref[2*C-i],R-i):1;
            while(i+pref[i]<str.size()&&i-pref[i]>-1){
                if(str[i+pref[i]]==str[i-pref[i]])pref[i]++;
                else break;
            }
            if(i+pref[i]>R){
                R = i+pref[i];
                C = i;
            }
        }
        int idx = -1;
        int temp = 0;
        for(int i = 0 ; i<str.size() ; i++){
            if(pref[i]>temp){
                temp = pref[i];
                idx = i;
            }
        }
        temp--;
        string res = "";
        for(int i = idx-temp ; i<=idx+temp ; i++){
            if(str[i]!='#')res+=str[i];
        }
        return res;
    }
};

KMP算法

用于字符串匹配的加速算法,通过next数组来进行加速匹配,常规的字符串匹配算法,是O(N^2)的时间复杂度,从一个字符开始逐个进行匹配,如果匹配不成功,从下一个字符开始重新进行操作,这样造成了大量的重复匹配和资源浪费。

next数组计算方法: KMP算法最关键最核心的在于next数组,每个字符串都对应了一个next数组,数组中每个值代表对应字符的最长前后缀,比如一个字符a,它之前的字符串为abab,它的前缀有a,ab,aba,后缀有b,ab,bab,那么最长的前后缀为ab,长度为2,所以该字符对应的next数组的值为2,这就是next数组的计算方法,但要注意,因为第一个字符和第二个字符是不能计算前后缀的,所以任何next数组,next[0]=-1,next[1]=0。

那么怎么进行加速呢,同样我们假设target字符串为ababc,根据我们之前提到的方法,next=[-1,0,0,1,2],如果我们的source字符串为abababc,当字符串匹配到(ababa-ababc)时,我们认为匹配不成功,传统做法我们需要从source的第二个字符开始重新匹配,但有了next数组,我们只需要将next[4]对应的字符移动到4处,然后从4开始匹配即可,因为next[4]=2,即现在为(ababa-xxaba)然后往下进行匹配,得到答案,(x代表空白位置),这就是根据next数组进行了匹配加速。

13 · 字符串查找 - LintCodeicon-default.png?t=M85Bhttps://www.lintcode.com/problem/13/description?showListFe=true&page=1&submissionStatus=ACCEPTED&pageSize=50

class Solution {
public:
    vector<int> next;

    int strStr(string &source, string &target) {
        if(target.size()==0)return 0;
        if(source.size()<target.size())return -1;
        next.resize(target.size());
        cal(target);
        int poss(0);
        int post(0);
        while(poss<source.size()&&post<target.size()){
            if(post==-1){
                post++;
                poss++;
            }
            if(target[post]==source[poss]){
                post++;
                poss++;
            }
            else{
                post = next[post];
            }
        }
        if(post==target.size())return poss-post;
        else return -1;
    }

    void cal(string str){
        next[0] = -1;
        next[1] = 0;
        int pref = 0;
        for(int i = 2 ; i<str.length() ;){
            if(str[i-1]==str[pref]){
                next[i] = next[i-1]+1;
                pref++;
                i++;
            }
            else{
                pref = next[pref];
                if(pref==-1){
                    i++;
                    pref++;
                }
            }
        }
    }
};

这里还遇到了一个问题,就是一个获取一个字符串长度的方法,.size(),获取长度后我使用了一个大小判断就出现了问题,就相当于str.size()==3,但是-1<str.size()的判定是false,然后自己debug发现确实存在这个问题,通过资料查找,发现.size()方法返回的数值为无符号整型,unsigned int,无符号整形和整形一起进行计算和大小比较会出现问题,所以在运算之前,最好将.size()得到的字符串长度转换为int,就没问题了。

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值