• kmp字符串匹配


    28. 实现 strStr()

    难度简单

    实现 strStr() 函数。

    给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回  -1。

    示例 1:

    输入: haystack = "hello", needle = "ll"
    输出: 2
    

    示例 2:

    输入: haystack = "aaaaa", needle = "bba"
    输出: -1
    

    说明:

    当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。

    对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。

    通过次数220,386
     
    提交次数555,007

     

    实现strStr( )---小白做法

    小天才发布于 10 天前578C++

    解题思路

    为大家推荐下面这个B站up主讲的KMP算法,超级好
    kmp原理:https://www.bilibili.com/video/BV1Px411z7Yo?from=search&seid=13225444196686531503
    kmp实现代码:https://www.bilibili.com/video/BV1hW411a7ys/?spm_id_from=333.788.videocard.0

    代码

    class Solution {
    public:
        int strStr(string haystack, string needle) 
        {
            if(!needle.size()) return 0;
            if(!haystack.size()) return -1;
            //构造前缀表
            int j = -1, i = 0;
            vector<int> b(needle.size() + 1);
            b[i] = j;
            while(i < needle.size())
            {
                while(j >= 0 && needle[i] != needle[j]) j = b[j];
                i++, j++;
                b[i] = j;
            }
            //KMP-search
            j = 0, i = 0; 
            while(j < haystack.size())
            {
                while(i >= 0 && needle[i] != haystack[j]) i = b[i];
                i++, j++;
                if(i == needle.size())
                {
                    return j - needle.size();
                }
            }
            return -1;
        }
    };

     

     

     

    KMP:花48小时看懂了KMP,想让你在48分钟内看懂 EL1S

    EL1S发布于 2020-03-137.7kC++

    点开了无数个KMP的讲解页面,点开文章中的各种跳转链接,居然被我发现了这样一篇好文章,代码写的好短,还有图:https://www.inf.hs-flensburg.de/lang/algorithmen/pattern/kmpen.htm 结合自己的理解分享一下。

    一、KMP的用途,作用对象

    第一个问题:KMP是干嘛用的?
    KMP是用来找字符串匹配的

    第二个问题:为什么要用KMP来找字符串匹配?
    KMP的时间复杂度是O(m + n),时间复杂度低

    我们拿一道题来做例子Leetcode28.实现 strStr()
    image.png

    这道题能想到几种方式?
    第一种:遍历haystack的每一个字符作为开头,needle.size()为长度和needle做对比,一样则返回这个开头的idx
    第二种: 枚举haystack的每一个字符作为开头,neddle.size()作为长度,扔到map<string, int>里面,去map里面找有没有needle
    第三种:本文介绍的KMP

    KMP的两个对象是什么:
    第一个:pattern->也就是题目中的needle
    第二个:text->也是就是题目中的haystack

    二、KMP的流程要分为两步:

    第一步:构建一个数组b,大小是pattern.size() + 1,进行Preprocess预处理对数组填充
    第二步:把pattern和text进行匹配,利用b数组加速匹配,找到那个出现的第一个位置的idx

    2.1 构建数组b

    在构建b数组之前,要让你们认识到b数组的必要性,也是就是它到底能怎么帮助我们加速匹配?
    text: ababbabaa
    pattern: ababac

    不用加速的时候是这样的(绿的匹配上了,红的匹配失败,黑的还没匹配):
    每次遇到不匹配的时候呢,头部就往右边挪一个位置
    image.png

    用加速的时候是这样的:
    每次遇到不匹配挪动的位置可能就不止一步了! 注意注意:不止一步不止一步!!上面那个是慢走大路,这个就是抄近路。这里上面的图和下面的图我都是画了5个step,似乎并没有加速?这个例子呢是因为字符长度比较短,对于字符串长度长的效果会很明显。
    下面这张图我是为了说明两个道理:1. 它不是一步一步的挪动的,是跳着挪动的!所以会快,因为不用匹配那么多次了呀;2. 而且仔细看,下面这些图有一些红色之前还是黑色!这些也是没有去重复匹配的,但是上面的图红色之前都是绿色,说明都有遍历匹配!
    image.png

    好了,现在我们知道是可以这样加速的,我们就有加速的rules!这些rules就存在前文提及到的数组b里面,也是就是说啊,当我pattern和text发生不匹配的时候啊,我要怎么往前跳一下?
    现在我们的问题是怎么构建这个数组b?
    让我们先来观察一下这个跳动是怎么跳的,为什么要这样跳?
    image.png

    一开始的时候是text[0-3] = "abab" 匹配上了pattern[0-3] = "abab",但是在text[4]和pattern[4]匹配的时候发现了不匹配,可惜之前匹配了那么一大串了,于是就有一个想法:能不能把之前匹配过的利用起来?
    我们把pattern进行滑动看看效果:
    image.png

    这张图应该让你明白是想要怎么保留利用之前匹配过了的,那么现在该看看这个b怎么建立了
    b这个数组的大小是pattern.size() + 1,数组里面存的东西的含义是,假设text[k]和pattern[i]没有匹配上,i要进行更新,更新成i = b[i], 然后再去text[k]和pattern[i]进行对比。

    建立这个b的代码是:

    int j = -1, i = 0;//j在后面,i在前面
    vector<int> b(needle.size() + 1);
    b[i] = j;
    while(i < needle.size())
    {
        while(j >= 0 && needle[i] != needle[j]) j = b[j];
        i++, j++;
        b[i] = j;
    }
    

    我给一个例子,搭配着图解释:
    image.png

    一开始,把j初始化为-1, i初始化为0,b[0]初始化为-1,为什么要这样初始化后面会讲,先跳过看一下效果。
    j >= 0不成立,所以没有进入while循环,i++ = 1 j++ = 0 b[1] = 0;是不是看了这波操作有一点懵圈?让我来理一下思路,这段代码在填充b的时候就是:背靠着山,未雨绸缪。意思是i = 0先保证了自己的匹配成功,然后去为i = 1考虑:若pattern在i = 1的时候和text匹配不上了,保留一段已经匹配过的,然后让pattern的哪个idx的字符接着和text去匹配

    看回我们之前的例子:
    image.png

    这里不就是i = 4的时候pattern和text匹配不上了,然后pattern保留了一段(即"ab"),然后让pattern idx = 2的字符接着和text进行匹配
    再来看看我们现在这个例子:
    image.png

    就是i = 0的时候就去为i = 1做打算,如果说pattern[i = 1]和text不匹配了,i就不能是1了该变到几?答案是0,去和pattern[i = 0]匹配吧
    这两个指针i, j,会努力维持pattern[i] = pattern[j],如果实在维持不了的话,j就是-1了; 因为代码里面有那个i++, j++,所以下一个i = 1其实要是j = 0去匹配

    继续看,现在是i = 1,j = 0,发现居然匹配不上,j很生气,就往左边跑,j = b[j = 0] = -1,然后i++ = 2, j++ = 0, 这里就是当i = 1的时候在为i = 2考虑啊了,i = 1先把自己匹配上,匹配不上j就到-1,然后i = 1告诉i = 2:b[2] = 0,意思是i = 2啊要是你pattern[i = 2]没有和text匹配上的话你去pattern[b[2]] = pattern[0]看看能不能匹配上。

    下一步是i = 2, j = 0;哈哈哈,有意思,我pattern[i]和pattern[j]一样的,于是i = 2告诉i = 3:我i = 2是匹配了成了的,你要是没和text匹配上,你就去和pattern[j + 1 = 1]匹配吧,因此b[i = 3] = j + 1 = 1

    再下一步,i = 3, j = 1;pattern[i] = pattern[j],匹配上了哦,i = 3告诉i = 4: b[i= 4] = j + 1 = 2
    接着,i = 4, j = 2, pattern[i] = pattern[j],嗯,很好,i = 4自己匹配上了,于是告诉i = 5: 你要是没和text匹配上啊你就和pattern[3]匹配去吧,故b[i = 5] = 3

    到了i = 5, j = 3; i = 5要让自己先匹配,然后为i = 6做打算,呀pattern[i]和pattern[j]居然不匹配,说明啊,i = 5得找到前面的一个j和自己有相同的prefix这样呢,才能为i = 6做打算,那么怎么找前面的j呢?i = 5心里思考:i= 4和我说了要是我匹配不上的话我可以往前, b[j = 3] = 1,于是i = 5就去和pattern[j = 1]做这个对比了,发现还不一样,只好再往前了,j = b[j = 1] = 0, pattern[0] = 'a' = pattern[5],好了终于相同了,可以为i = 6做谋划了,i = 6就是要走i = 5的后尘下一步,b[6] = j + 1 = 0 + 1 = 1

    2.2 利用数组b

    b数组构建好了,到了KMP的第二大步骤了:把b数组用起来!

    j = 0, i = 0; //j这回是text的, i是pattern的
    while(j < haystack.size())
    {
        while(i >= 0 && needle[i] != haystack[j]) i = b[i];
        i++, j++;
        if(i == needle.size())
        {
            return j - needle.size();
        }
    }
    return -1;
    

    i是指向pattern的指针,j是指向text的指针,如果text[j] == pattern[i],那就i++, j++继续向后比对就是了,

    如果text[j] != pattern[i]呢,这个时候就是pattern开始跳跃的时候了, text[j] != pattern[i]是吧,pattern说:那我看看我的rules记录本b,b[i]告诉我我要跳到这个位置,好,于是i = b[i]; text[j]再和pattern[i]比过,如果相同那么就继续向后,不过不同,那i继续向前跳,如果i都跳到-1了都没有找到pattern[i] = text[j],说明啊这个text[j]我的pattern的前缀里面就是没有合适的,那行吧,j你加一吧,我i从0开始重新开始匹配。

    三、题目KMP解法代码

    class Solution {
    public:
        int strStr(string haystack, string needle) {
            if(!needle.size()) return 0;
            if(!haystack.size()) return -1;
            //先构造pattern
            int j = -1, i = 0;//j在后面,i在前面
            vector<int> b(needle.size() + 1);
            b[i] = j;
            while(i < needle.size())
            {
                while(j >= 0 && needle[i] != needle[j]) j = b[j];
                i++, j++;
                b[i] = j;
            }
            
            j = 0, i = 0; //j这回是text的, i是pattern的
            while(j < haystack.size())
            {
                while(i >= 0 && needle[i] != haystack[j]) i = b[i];
                i++, j++;
                if(i == needle.size())
                {
                    return j - needle.size();
                }
            }
            return -1;
        }
    };
    主动一点,世界会更大!
  • 相关阅读:
    7 重排序与happens-before
    6 Java内存模型基础知识
    5 Java线程间的通信
    Java线程的状态及主要转化方法
    《The Boost C++ Libraries》 第一章 智能指针
    python通过swig调用静态库
    使用gdb调试
    Rsync服务部署使用
    UNP学习总结(二)
    read()函数的困惑
  • 原文地址:https://www.cnblogs.com/sweet-li/p/13566897.html
Copyright © 2020-2023  润新知