[ kmp ]28. 实现 strStr()

136 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第12天,点击查看活动详情

每日刷题 2021.04.12

题目

  • 实现strStr()函数。
  • 给你两个字符串 haystackneedle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回-1
  • 说明
  • 当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
  • 对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java 的 indexOf() 定义相符。

示例

  • 示例1
输入: haystack = "hello", needle = "ll"
输出: 2
  • 示例2
输入: haystack = "aaaaa", needle = "bba"
输出: -1
  • 示例3
输入: haystack = "", needle = ""
输出: 0

提示

  • 1 <= haystack.length, needle.length <= 10^4
  • haystack 和 needle 仅由小写英文字符组成

解题思路

  • 常规的做法:双层for循环,10 ^ 8次方,实际运行没有超时,可以正常通过。但是如果题目中的数据给到10 ^ 5 ~ 10 ^ 6的话,就需要使用KMP字符串匹配算法来解决问题。
  • 进阶做法kmp,因为kmp的核心思想不太好理解,因此选择简单题作为入门。

KMP算法

  • KMP本质是:优化常规做法中,每次都从头比较匹配串的操作。如何快速在原串中找到匹配字符串的下标。
  • kmp因为在非完全匹配的过程中(即:失败的匹配过程中),提取有效信息进行复用,以此来减少重复的匹配操作,优化时间复杂度。
  • kmp核心思想:不相等时,回退。
  • 主要分为2部分:
    • 匹配串的next数组
    • 原串和匹配串的匹配过程
  • 第一部分:next数组
    • 匹配串和自身进行匹配,所形成的数组。
    • 首先我们要明白两个概念:前缀:一个字符串中不包含最后一个元素的连续的子串;后缀:一个字符串中不包含最开始一个元素的连续的子串
      • 举例:字符串:bkdjsbk,其前缀[b,bk,bkd,bkdj,bkdjs,bkdjsb];后缀:[k,bk,sbk,jsbk,djsbk,kdjsbk]。比较前缀集合和后缀集合,从中找到相等的最长的子串bk
    • 现在我们知道了最长相等前缀是什么含义后。
  • 第二部分:原串和匹配串的匹配过程
    • 方法与求解next数组的方法相似

拓展

  • kmpnext数组中的值,还可以统一右移,第一位赋值为-1;还可以全部减1
  • 以上这些处理方式,全部是在实战中更方便的处理方式。

AC代码

var strStr = function(haystack, needle) {
  // 空字符串,返回0,因为空字符串是最小的子串
  if(needle == '') return 0;
  if(needle.length > haystack.length) return -1;
  // 不存在的话,返回-1
  // 使用kmp,核心就是next数组,也就是不符合就回退
  function getNext(needle) {
    // 模式串自己和自己进行比较
    let len = needle.length;
    let next = new Array(len).fill(0);
    for(let i = 1,j = 0; i < len; i++) {
      while(needle[i] != needle[j] && j > 0){
        // 如果不想等,就需要回退
        j = next[j - 1];
      }
      if(needle[i] == needle[j]){
        // 如果相等,那么当前的最长前缀+1
        j++;
      }
      next[i] = j;
    }
    return next;
  }
  next = getNext(needle)
  // 存在的话,返回第一次出现的下标
  // 根据next数组,填充对应的比较
  let lenH = haystack.length,index = -1,flag = true,len = needle.length;
  for(let i = 0,j = 0; i < lenH; i++) {
    while (haystack[i] !== needle[j] && j > 0){
      j = next[j - 1];
    }
    if(haystack[i] == needle[j]){
      j++;
    }
    if(j == len) {
      index = i - len + 1;
      break;
    }
  }
  // 最后都存在不相等的,需要找下一个开头
  return index;
};

总结

  • KMP算法的时间复杂度o(m + n),其中m表示原串的长度,n表示匹配串的长度。
  • 常规的双层for循环,尽管可以只遍历到m - n长度,但是时间复杂度仍然是o(m * n)