KMP 算法

37 阅读3分钟

题目

给你两个字符串 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 <= 104
  • haystack 和 needle 仅由小写英文字符组成

作用

KMP 算法的作用是当出现字符串不匹配时,可以记录一部分之前已经匹配的内容,避免从头再匹配。

前缀表

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

例子:

要在文本串:aabaabaafa 中查找是否出现过一个模式串:aabaaf。

前缀表要求的其实是相同前后缀的长度。

这里面有两个概念,前缀和后缀。

前缀:指不包含最后一个字符的所有以第一个字符开头的连续子串。

后缀:指不包含第一个字符的所有以最后一个字符结尾的连续子串。

如何求前缀表

截屏2025-09-20 20.53.27.png

根据上图,可以口算一下每一个子串的相同前后缀的长度。

前缀:指不包含最后一个字符的所有以第一个字符开头的连续子串。

后缀:指不包含第一个字符的所有以最后一个字符结尾的连续子串。

  • 子串为 a 时,相同前后缀长度为 0 。
  • 子串为 aa 时,相同前后缀长度为 1 。
  • 子串为 aab 时,相同前后缀长度为 0 。
  • 子串为 aaba 时,相同前后缀长度为 1 。
  • 子串为 aabaa 时,相同前后缀长度为 2 。
  • 子串为 aabaaf 时,相同前后缀长度为 0 。

如下图所示:

截屏2025-09-20 20.57.33.png

截屏2025-09-20 20.57.33.png

实现

前缀表实现时可以统一减一。如下图所示:

截屏2025-09-20 22.00.44.png

先贴代码,再分析。

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

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

    return next;
}

首先,需要明确概念。

  • next 数组每个元素是模式串即 aabaaf 每个下标对应的相同前后缀的长度。 next[0] 对应的是 aabaaf 中 字符 a 的相同前后缀长度。同时也是相同前后缀中前缀尾元素下标(统一减一后)。

  • 代码中的 j 对应的就是模式串下标为 i 时,最长相等前后缀的长度。

  • 因为 next 数组统一减一,所以将 j 初始化为 -1.

在明确了这几个概念之后,代码就很容易理解了。就是比较相同前后缀中的前缀的后一个元素即needle[j + 1]和相同前后缀中后后缀的后一个元素即needle[i],并进行操作。

题目解法

这道题目,逻辑和求 next 数组很相似。

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

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

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

    return next;
}

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

return -1;

};