找出字符串中第一个匹配项的下标

给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1 。

普通方法

var strStr = function (haystack, needle) {
  let hLen = haystack.length;
  let nLen = needle.length;
  // 从第一个开始向后匹配
  for (let i = 0; i <= hLen - nLen; i++) {
    // 原始字符串的匹配位置
    let a = i;
    // 目标字符串的起始位置
    let b = 0;
    while (b < nLen && haystack[a] === needle[b]) {
      a++;
      b++
    }
    // 匹配成功返回起始位置
    if (b === nLen) return i
  }
  return -1
};

KMP 算法

在匹配出错时不把字符串完全回退重新下一位匹配,找到前缀表进行回退后继续匹配

前缀表 next数组

next数组就可以是前缀表,但是很多实现都是把前缀表统一减一(右移一位,初始位置为-1)之后作为next数组。

const next = new Array(needle.length).fill(0);
/**
    i:当前子串的后缀末尾
    j:上一项子串最长相等前后缀的下一项 或者 0 ,并且也是 上一项子串最长相等前后缀的长度

    i从1开始,j从0开始。因为 i 和 j都是0的话只有一个,肯定是0,相当于上面例子中的 a
*/
for (let i = 1, j = 0; i < m; i++) {
  1
  // while循环,要不断的向上寻找。
  while (j > 0 && needle[i] !== needle[j]) {
    // 如果不同,我们要从未匹配好的地方开始继续匹配。
    // 未匹配好的位置是那里呢? 👇
    // 我们知道 next 数组的值就代表每一次子串匹配好的长度,
    // 因为数组是从0开始的,所以j - 1就指向了上一个子串未匹配好的位置。
    // 当j === 0时,说明要从头开始重新匹配了
    j = next[j - 1];
  }
  // 如果当前子串前后缀相等,就将更新子串,j++ i++
  if (needle[i] == needle[j]) {
    j++;
  }
  // 将当前子串的最长相等前后缀添加到next数组中
  next[i] = j;
}
var strStr = function (haystack, needle) {
  let n = haystack.length
  let m = needle.length
  if (m === 0) return 0

  let next = new Array(m).fill(0)
  for (let i = 1, j = 0; i < m; i++) {
    while (j > 0 && needle[i] !== needle[j]) {
      j = next[j - 1]
    }
    if (needle[i] === needle[j]) {
      j++
    }
    next[i] = j
  }
  
  for (let i = 0, j = 0; i < n; i++) {
    // 如果当前i 和 j不一致,就回退到上一个相等的位置的下一个看看是否匹配
    // 会不断回退,0为回退到边界,当回退到0意味着要重新从头开始匹配
    while (j > 0 && haystack[i] !== needle[j]) {
      j = next[j - 1]
    }
    if (haystack[i] === needle[j]) {
      j++
    }
    // 当j 和 m的长度相等时,就说明存在
    if (j === m) {
      return i - m + 1
    }
  }
  return -1
};
贡献者: huxiguo