算法 实现substr() KMP算法

实现 strStr() 函数。

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

示例 1:

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

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

输入:haystack = "", needle = ""
输出:0
 

提示:

0 <= haystack.length, needle.length <= 5 * 104
haystack 和 needle 仅由小写英文字符组成

用KMP算法的解决的是什么问题:

如果主串和模式串发生匹配冲突,通过前缀表也就是next数组,能够记录前面匹配过的一些内容,从而让模式串的指针不会从头开始匹配,从而提升效率。

知道下面的几个概念和KMP算法的核心,这个解法就理解了。后续会补充。

next数组【前缀表】是什么

next数组怎么求

前缀和后缀

最长相等前缀和后缀

看代码前,附上我看的视频链接,看了2-3遍视频,才能搞懂,我比较笨。

视频链接:

帮你把KMP算法学个通透!(求next数组代码篇)_哔哩哔哩_bilibili

帮你把KMP算法学个通透!(理论篇)_哔哩哔哩_bilibili

代码随想录网站:

代码随想录

  纯净无注释代码 再下面有 “注释版代码”

            var strStr = function (haystack, needle) {
            if (needle.length === 0)
                return 0;

            const getNext = (needle) => {
                let next = [];
                let j = 0;
                next.push(j);

                for (let i = 1; i < needle.length; ++i) {
                    while (j > 0 && needle[i] !== needle[j])
                        j = next[j - 1];
                    if (needle[i] === needle[j])
                        j++;
                    next.push(j);
                }

                return next;
            }

            let next = getNext(needle);
            let j = 0;
            for (let i = 0; i < haystack.length; ++i) {
                while (j > 0 && haystack[i] !== needle[j])
                    j = next[j - 1];
                if (haystack[i] === needle[j])
                    j++;
                if (j === needle.length)
                    return (i - needle.length + 1);
            }

            return -1;
        };

注释版本代码

        // 注释版本代码
        var strStr = function (haystack, needle) {
            if (needle.length === 0)
                return 0;
            const getNext = (needle) => {
                // 1. 初始化 next数组 用needle子串生成next数组
                let next = [];
                // 2. 初始化 j指针 
                // 求最长相等前缀和后缀的长度
                // j => 前缀末尾 / 也代表 i【包括i】之前的子串的最长相等前后缀的长度
                // i => 后缀末尾 for循环初始化
                let j = 0;
                // j初始化为0
                next.push(j);
                // next数组的每一位的值 都是记录当前字符之前的子串的最长相等前后缀的长度
                // a =》next[0]
                // aa =》next[0,1]
                // aab =》next[0,1,0] 此时最长先等前后缀长度是0 a和b不同
                // aaba =》next[0,1,0,1]
                // aabaa =》next[0,1,0,1,2]
                // aabaaa =》next[0,1,0,1,2,1]
                for (let i = 1; i < needle.length; ++i) {
                    while (j > 0 && needle[i] !== needle[j])
                        // 0. 这里用while循环是关键 因为j的回退不是只回退一次 而是很多次
                        // 1. j从0开始移动,i从1开始移动 
                        // 2. 一旦指针指的值不同 ,j要回退,回退到哪里是关键
                        // 3. next[j - 1]比如是0,j就回退到索引0的位置
                        // 4. next[j - 1]比如是1,j就回退到索引1的位置 因此如下赋值
                        j = next[j - 1];
                    if (needle[i] === needle[j])
                        // 5. 如果相同
                        // j++ i++ i是for循环自增 
                        j++;
                    next.push(j);
                    // 6. next.push(j) 必须放到末尾是关键 不管是冲突还是相等, 都应该往next数组里面push值
                }

                return next;
            }

            let next = getNext(needle);
            // 上面的next数组生成好了 比如是[0, 1, 0, 1, 2, 0]
            let j = 0;
            for (let i = 0; i < haystack.length; ++i) {
                // 这里是i和j意义不同
                // i遍历主串
                // j遍历模式串
                while (j > 0 && haystack[i] !== needle[j])
                    // 这里看似代码类似,但实际是匹配主串和模式串
                    // 如果不等就让j回退到一个位置,前一个j-1对应next数组的值,比如next[j - 1] 是 3 就回退到索引3
                    // 如果下面相等 就++
                    j = next[j - 1];
                if (haystack[i] === needle[j])
                    j++;
                // 如果j到头了,就返回
                if (j === needle.length)
                    return (i - needle.length + 1);
                    // 这里return i - needle.length + 1是我没想到的
                    // 但是仔细一对比 画个图就可以看出来了
            }

            return -1;
        };
        console.log(strStr('aabaabaaf', 'aabaaf')); // 3
        console.log(strStr('aabaabaafdaabaafg', 'aabaafg')); // 10

上面的代码也是从“代码随想录"摘的,但是注释是我自己加滴。

再次感谢carl大哥的讲解

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值