Day9|KMP算法一刷

之前教练讲过kmp算法所以比较熟悉,但当时没有掌握的很熟练,只是知道大概原理,今天比较细致的刷了一遍,还是很有收获的。

首先kmp算法就是在匹配字符串时效率化,正常的暴力法是两个for循环,一旦模式串和文本串不匹配,就要从文本串的下一个点开始,(模式串重新开始)匹配,费时间。kmp算法的关键就是,在匹配不成功的时候,模式串可以不重新开始,直接从之前记录的点开始(具有跳到之前已经匹配过的地方的能力)。那凭什么能找到之前记录的那个点呢?就是靠的最长相等前后缀,因为找到了最长相等的前缀和后缀,匹配失败的位置是后缀子串的后面,那么我们找到与其相同的前缀的后面重新匹配就好了,(前缀重复的部分就可以省略,这就是效率化)。 那怎么记录最长相等前后缀,全靠前缀表,我们在这里用next存储,先给大家一张图片

(前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串;后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。)

长度为前1个字符的子串a,最长相同前后缀的长度为0。

长度为前2个字符的子串aa,最长相同前后缀的长度为1。

长度为前3个字符的子串aab,最长相同前后缀的长度为0。 

长度为前4个字符的子串aaba,最长相同前后缀的长度为1。

长度为前5个字符的子串aabaa,最长相同前后缀的长度为2。

长度为前6个字符的子串aabaaf,最长相同前后缀的长度为0。

可以看出模式串与前缀表对应位置的数字表示的就是:下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。

找到的不匹配的位置, 那么此时我们要看它的前一个字符的前缀表的数值是多少。为什么要前一个字符的前缀表的数值呢,(此时指针所指的位置已经不匹配成功了)因为要找前面字符串的最长相同的前缀和后缀。所以要看前一位的 前缀表的数值。前一个字符的前缀表的数值是2, 所以把下标移动到下标2的位置继续比配。 最后就在文本串中找到了和模式串匹配的子串了。next的储存方式有好多种:我这里默认取的是前缀表不减一

Leetcode 28. 找出字符串中第一个匹配项的下标

题目链接 28 找出字符串中第一个匹配项的下标

上代码:

class Solution {
public:
void getNext(int *next,const string &s){//对模式串进行求next前缀表的操作,这里是为了使用前缀表方便告诉我们匹配失败之后,
//我们应该跳到哪里去重新匹配
    int j = 0;//这里采用的前缀表是不减一的形式,而是右移的情况
    next[0] = 0;//初始化
    //这里是用来求最长相等前后缀
    for(int i=1;i<s.size();i++){//i指针起遍历作用,不停的往前
        while(j>0&&s[i]!=s[j]){//j等于0时无法前移,如果前缀和后缀字母不同,并且j不是首元素,就将j等于前一个的next值
                    j = next[j-1];//next[j-1]就是记录着j,包括j之前的子串的相同前后缀的长度
        }
        if(s[i]==s[j]){//如果相等j指针就右移
            j++;
        }
        next[i] = j;//将j赋值给next,此时j记录着当前i的子串的相同前后缀的长度
    }
}
    int strStr(string haystack, string needle) {
        if(needle.size() == 0){
            return 0;
        }
        int next[needle.size()];
        getNext(next,needle);
        int j = 0;
        for(int i=0;i<haystack.size();i++){
                while(j>0&&haystack[i]!=needle[j]){
                    j = next[j-1];//匹配不上,就寻找之前匹配的位置(前缀表的作用),缩短匹配时间
                }
                if(haystack[i] == needle[j]){//匹配上就继续匹配
                    j++;
                }
                if(j == needle.size()){//如果等于字符串的长度,说明匹配完成了
                    return (i - needle.size() + 1) ;//返回haystack中的位置
                }
        }
        return -1;
    }
};

Leetcode 459. 重复的子字符串

题目链接 459 重复的子字符串

两种方法:

第一种 移动匹配法,这个我觉得是非常难想的,给个图:

一个字符串s:abcabc,如果满足题目要求的话,必须有前后相同的子串组成,那么既然前面有相同的子串,后面有相同的子串,用 s + s,这样组成的字符串中,后面的子串做前串,前面的子串做后串,就一定还能组成一个s。(所以判断字符串s是否由重复子串组成,只要两个s拼接在一起,里面还出现一个s的话,就说明是由重复子串组成。)

当然,我们在判断 s + s 拼接的字符串里是否出现一个s的的时候,要刨除 s + s 的首字符和尾字符,这样避免在s+s中搜索出原来的s,我们要搜索的是中间拼接出来的s。

下面上代码:

class Solution {
public:
    bool repeatedSubstringPattern(string s) {
        string ss = s+s;
        ss.erase(ss.begin());
        ss.erase(ss.end()-1);//c.end();返回指向容器最后一个数据单元+1的指针
        if(ss.find(s)!=std::string::npos){ //npos 是一个常数,用来表示不存在的位置
        //这里的if语句意义:没有发现不存在,即发现了s
            return true;
        }
        return false;
    }
};
};

kmp法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值