KMP 算法

字符串匹配是计算机的基本任务之一。

举例:字符串 "BBC ABCDAB ABCDABCDABDE" 是否包含 "ABCDABD"?Knuth-Morris-Pratt 算法(简称 KMP)是最常用的之一,以三个发明者命名,起头的 K 就是著名科学家 Donald Knuth。

1. 朴素匹配的低效

朴素的逐位匹配,每次失配就回退搜索词的起点,最坏情况时间复杂度 O(m·n)。

2. KMP 的核心思想

利用已知匹配信息,避免回退搜索词。

当空格与 D 不匹配时,前面 6 个字符 "ABCDAB" 是匹配的。这时不需要把搜索词整个回退到起点,而是根据"部分匹配表"决定后移几位:

移动位数 = 已匹配的字符数 - 对应的部分匹配值

3. 部分匹配表(Partial Match Table)

"部分匹配值" = "前缀"和"后缀"的最长共有元素的长度。

以 "ABCDABD" 为例:

子串前缀后缀共有元素部分匹配值
A---0
AB[A][B]-0
ABC[A, AB][BC, C]-0
ABCD[A, AB, ABC][BCD, CD, D]-0
ABCDA[A..ABCDA][BCDA, CDA, DA, A]A1
ABCDAB[A..ABCDA][BCDAB..B]AB2
ABCDABD[A..ABCDAB][BCDABD..D]-0

4. JavaScript 实现

let strStr = function (hayStack, needle) {
  if (needle.length === 0) return 0;

  // 构建 next(部分匹配表)
  const getNext = (needle) => {
    const next = [];
    let j = 0;
    next.push(j);
    for (let i = 1; i < needle.length; i++) {
      while (j > 0 && needle[j] !== needle[i]) j = next[j - 1];
      if (needle[j] === needle[i]) j++;
      next.push(j);
    }
    return next;
  };

  const 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;
};

5. 复杂度

  • 时间复杂度:O(m + n)(构建 next 是 O(m),匹配是 O(n))。
  • 空间复杂度:O(m)。

来源整理自:我的有道云笔记