隐藏
Single

KMP算法

首先通过一道题目引入KMP,他妈的硬控了我几个小时研究透彻,值得总结总结:

28. 找出字符串中第一个匹配项的下标,这是KMP算法的经典题目,暴力解决一点不难,但用上KMP就值得研究了。

给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回  -1 。

示例 1:

输入:haystack = “sadbutsad”, needle = “sad”
输出:0
解释:”sad” 在下标 0 和 6 处匹配。
第一个匹配项的下标是 0 ,所以返回 0 。

示例 2:

输入:haystack = “leetcode”, needle = “leeto”
输出:-1
解释:”leeto” 没有在 “leetcode” 中出现,所以返回 -1 。

KMP的经典思想就是:当出现字符串不匹配时,可以记录一部分之前已经匹配的文本内容,利用这些信息避免从头再去做匹配。

为了记录已经匹配的文本,需要一个next数组,这就是KMP的关键。

next数组,顾名思义,记录下一个要匹配的元素的位置,本质上就是一个前缀表。

前缀表是用来回退的,它记录了模式串文本串不匹配的时候,模式串应该从哪里开始重新匹配。

对于本题来说,文本串就是haystack,模式串就是needle。

比如说

文本串:a a b a a b a a f

模式串:a a b a a f

当遍历到第六个,这时文本串是b模式串是f,发现不相等,这时候模式串只要回退到b来与文本串的b继续比较就好了。

那么怎么确定回退的位置呢?

比如以上情况,怎么回退到b呢?查next数组,发现next数组记录的是2,于是回退到needle[2]

所以就需要我们在遍历比较之前,先把next数组设置好!!

void getNext(int* next, const string& s)//s是模式串

构造next数组其实就是计算模式串前缀表的过程。 主要有如下三步:

1.初始化
2.处理前缀相同的情况
3.处理前缀不相同的情况

1.初始化:

要拿当前遍历到的元素和前面的元素做对比,遍历从next[1]开始。因此,必须对next[0]初始化为0;

next[0] = 0;

int j=0;//双指针法,j为要插入next的数字。

2.处理前缀相同的情况:

用for(int i = 1; i < s.size(); i++)进行遍历

如果 s[i] 与 s[j ] 相同,找到了j个相同的前后缀,同时还要将j(前缀的长度)赋给next[i], 因为next[i]要记录相同前后缀的长度。

if (s[i] == s[j]) {
                j++;
            }
            next[i] = j;

3.处理前缀不相同的情况:

while s[i] != s[j],需要回退,回退为j = next[j – 1];(这是啥?你先别急)

因为回退到0就不能回退了,还得加上j>0的条件。

关键的关键的关键,就是在于为什么为什么为什么是j=next[j-1]??而且用while来回退多次!!

 

我刚开始做这道题的时候,我当时啥比了,直接if(s[i]!=s[j])  j=0;

直接令j=0的话,对于一些情况是不可行的:

 

比如模式串为aabaaac,遍历到第六个(a),a!=b。

但第七个(c)应该继续和b进行比较,因为前面都有两个a。

也就是此时j=2而不是j=0;

实际上求next数组的过程相当于自身去求相同的字符串。

有了next数组,最后实现代码并不难:

class Solution {
public:
    void getNext(int* next, const string& s) {
        int j = 0;
        next[0] = 0;
        for(int i = 1; i < s.size(); i++) {
            while (j > 0 && s[i] != s[j]) {
                j = next[j - 1];
            }
            if (s[i] == s[j]) {
                j++;
            }
            next[i] = j;
        }
    }
    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);
            }
        }
        return -1;
    }
};

 

 

另一道可以用KMP的题,不过更简单一些:459.重复的子字符串

手搓的代码如下:

class Solution {
public:
    void getNext(int* next,const string& s){
        next[0]=0;
        int j=0;
        for(int i=1;i<s.size();i++){
            while(s[i]!=s[j]&&j>0){
                j=next[j-1];
            }
            if(s[i]==s[j]){
                j++;
            }
            next[i]=j;
        }
    }
    bool repeatedSubstringPattern(string s) {
        if(s.size()==0){
            return false;
        }
        int next[s.size()];
        getNext(next,s);
        if(next[s.size()-1]>0&&s.size()%(s.size()-next[s.size()-1])==0){
            return true;
        }
        else{
            return false;
        }
    }
};

 

 

暂无评论

发表评论

HTMLCOPY