字符串匹配——烦人的KMP

        相信很多同学看到这篇文章的时候,已经被KMP拿捏了吧!KMP算法说难,倒也不是很难,手算都会,说不难吧,短短几行代码愣是看不懂,辗转反侧,翻书查阅,视频讲解,最后还是一头雾水,百思不得其解(俺也一样.jpg),去年准备初试的时候,学到这里觉得这个算法很神奇,就想搞懂它,但是想了好几天都不太理解,考试也不考代码,就暂且搁置了,奈何复试要上机,故打开力扣刷刷题,开篇就看到KMP,又唤起了我那该死的记忆,昨晚冥思苦想一个半小时,终于悟出正道,来跟大家分享分享……(好久没更博客了,有点话痨)

        还不会手动模拟的同学,可以先去学习一下它的原理,我觉得王道咸鱼学长这个讲得不错4.2_2_KMP算法(旧版上)_哔哩哔哩_bilibili。接下来,完成这道题,就一起反向拿捏这个烦人的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 。
提示:1 <= haystack.length, needle.length <= 104haystack和 needle仅由小写英文字符组成​​​​​

理解next数组:

先举个例子理解一下:模式串为ababaa,手算可得出其next数组为:011234,next数组执行如下:

从图中可发现,每次回溯,前面都是有相同匹配的子串,本身匹不匹配不重要(我之前理解是,本身也要匹配,例如,第四位的b回溯到第二位的b,第四位的b前面是aba,第二位的b前面是a,有重复的子串a;第六位的a回溯到第四位的b,第六位的a前面是ababa,第四位的b前面是aba,有重复的子串aba,但a和b并不相等;再来看代码,

void getNext(int next[], string s)
{
    next[0]=-1;
    int i=0, j=-1;
    while(i<s.length())
    {
        if(j==-1||s[i]==s[j])//前面都匹配
        {
            i++;
            j++;
            next[i]=j;
        }
        else
        {
            j=next[j];
        }
    }
}

大家可以想象一下这个场景,拿两个相同的串进行匹配,一前一后,相差一个位置,如果相同,说明到目前为止我都是跟你相同的,而i++、j++,使得 i 指针和 j 指针之前都是相同的(准确的来说是有相同子串),则next[ i ] = j,表示当 i 不匹配的时候,可以跳到 j。如果这两个指针所指的不相等,说明 i 指针和 j 指针之前也是相同的,但是 i 指针和 j 指针失配了,只能让 j = next[ j ],让 j 回溯,j 去找跟它前面相同的,让 i 指针重新和新的 j 指针匹对;重复以上工作……


理解nextval数组:

这个就比next数组容易理解多了,还是拿上面的例子:模式串为ababaa,next数组为:011234,nextval数组为:010104;

例如,当第三位的a失配时,根据next数组要回溯到第一位的a,但是当第三位的a失配时,说明该元素不可能是a,回溯到第一位的a匹配则是多余操作,可以直接跳到它的next;当第六位的a失配时,根据next数组要回溯到第四位的b,因为a失配,只能说明该元素不是a,并不知道它是不是b,所以还要进行匹对;

代码如下:(应该不难理解)

void getNextval(int nextval[], int next[], string s)
{
    nextval[0]=-1;
    for(int i=1; i<s.length(); i++)
    {
        if(s[i]==s[next[i]])//该元素等于next里面的元素,因为字符串的首索引是0
        {
            nextval[i]=nextval[next[i]];
        }
        else
        {
            nextval[i]=next[i];
        }
    }
}

最后附上本题的ac代码:

class Solution {
public:
    int strStr(string haystack, string needle)
    {
        int next[10007]={0}, nextval[10007]={0};
        getNext(next, needle);
        getNextval(nextval, next, needle);
        int i=-1, j=-1;//i是主串,j是模式串
        int flag=0;//记录是否匹配模式串
        while(1)
        {
            if(j==-1||haystack[i]==needle[j])
            {
                i++;
                j++;
            }
            else
            {
                j=nextval[j];
            }
            if(j==needle.length())//匹配成功
            {
                flag=i-needle.length();
                return flag;
            }
            if(i==haystack.length())return -1;
        }
    }
    void getNext(int next[], string s)
    {
        next[0]=-1;
        int i=0, j=-1;
        while(i<s.length())
        {
            if(j==-1||s[i]==s[j])//前面都匹配
            {
                i++;
                j++;
                next[i]=j;
            }
            else
            {
                j=next[j];
            }
        }
    }
    void getNextval(int nextval[], int next[], string s)
    {
        nextval[0]=-1;
        for(int i=1; i<s.length(); i++)
        {
            if(s[i]==s[next[i]])//该元素等于next里面的元素,因为字符串的首索引是0
            {
                nextval[i]=nextval[next[i]];
            }
            else
            {
                nextval[i]=next[i];
            }
        }
    }
};

可能大家还有一点疑惑:为什么我的下标是从-1开始,因为字符串的首字符下标是0,而书上的那些例题讲解都是用字符数组存储字符串,它的首字符下标是从1开始。

建议看懂理解了的同学去力扣重新做一下这道题,加深印象。希望能帮助到大家,欢迎指正!

  • 22
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值