【算法之字符串】KMP算法

KMP算法

KMP的经典思想就是:当出现字符串不匹配时,可以记录一部分之前已经匹配的文本内容,利用这些信息避免从头再去做匹配

给出一个文本串 aabaabaaf ,一个模式串 aabaaf ,看文本串中是否出现过这个模式串
前缀表(next数组):帮助找到之前已经匹配过的内容(即next数组存储当前子字符串的最长相等前缀和后缀的长度)

  • a 的最长相等前后缀 为0
  • aa 的最长相等前后缀 为1
  • aab 的最长相等前后缀 为0
  • aaba 的最长相等前后缀 为1(a)
  • aabaa 的最长相等前后缀 为2(aa)
  • aabaaf 的最长相等前后缀 为 0

因此模式串aabaaf的前缀表是 0 1 0 1 2 0,当文本串和模版串进行比较时,在f出现不匹配的情况,f前面的前缀串的最长相等前后缀长度为2(意味着,这个子串有一个后缀aa,也有一个前缀aa,我们从前缀aa的后面进行下一次匹配)

前缀:包含首字母,不包含尾字母的字符串的所有子字符串(a、aa、aab、aaba、aabaa)
后缀:不包含首字母,包含尾字母的字符串的所有子字符串(f、af、aaf、baaf、abaaf)

步骤一:求next数组(不减一版本)

next()数组:当某个字符出现冲突,next数组告诉我们要回退到哪一个位置

思路:在包含i的子字符串里:

  1. 只要(while)前缀最后一个字符不匹配后缀最后一个字符,就将j回退到可能匹配的位置,再更新next数组;
  2. 如果(if)前缀最后一个字符匹配后缀最后一个字符,就将j++,再更新next数组
  3. j代表包含i的子字符串里最长相等前后缀的长度,所以更新next数组,就是将j赋值给当前(索引为i)的next数组元素next[i]=j
// 求一个模板字符串的next数组

function getNext(s) {
  let j = 0; 
  // j指向前缀字符串的末尾 ==> 
  // j代表s字符串的一个子串(该子串就是i之前的字符串,且包括i) 的最长相等前后缀的长度
  
  let next = new Array(s.length); 
  // 前缀表是一个数组,长度为字符串s的长度
  
  // 初始化next数组
  // next数组的第i个元素,是包括i之前的字符串的最长相等前后缀
  next[0] = 0; 
  
  // 在for循环里比较前后缀是否相等
  for (let i = 1; i < s.length; i++) {
    // i指向后缀字符串的末尾(子字符串长度至少为2,才会有后缀字符串,因此i指向第二个字符)
    // i从1一直遍历到字符串末尾(s.length-1)    

    // 1. 只要前缀最后一个字符不匹配后缀最后一个字符  j回退一次 j回退到哪里??
    // while循环确保:当i指向某个字符s[i]时,j回退到满足 s[j] == s[i] 的位置 
    // 如果此处用if的话,只会进行一次判断,j可能无法回退到正确的位置
    while (j > 0 && s[j] !== s[i]) {
      j = next[j - 1];
      // 这里为什么不是j--
      // j = next[j - 1]; 实际上是将j回退到上一个可能的匹配位置,而不是简单地j--,提高了匹配效率
      // next[j-1] 存储的是 长度为j-1的子字符串中, 最长相等前后缀的长度
      // 只要j>0(因为字符串长度至少为2,才可能有最长相等前后缀),就将j一直回退
    }
    // 将j回退完毕后,更新当前i的next数组
    // next[i] = 0;

    // 2. 如果子字符串里,前缀最后一个字符 与 后缀最后一个字符 匹配,则递增 j,而不需要回退
    if (s[j] == s[i])  j++;
    
     next[i] = j;
    
  }
  return next;
}

步骤二:判断文本串中是否有模板串

参考力扣28

var strStr = function (haystack, needle) {
  // 如果needle的长度为0,直接return 0
  if (needle.length === 0) return 0;

  // 1. 得到needle的next数组
  const needleNext = getNext(needle);

  // 2. 利用前缀表,找到"当字符不匹配时,指向needle的指针j应该移动到哪里"
  let j = 0; // j指向needle字符串
  for (let i = 0; i < haystack.length; i++) {
    // i指向haystack字符串

   
    while (j > 0 && haystack[i] !== needle[j]) {
      j = needleNext[j - 1];
    }

    // 4. 如果haystack和needle的字符匹配了,就让j++,进入下一个for循环
    if (haystack[i] == needle[j]) j++;

    // 5. 在匹配的过程中,如果j === needle.length,说明在haystack找到了与needle完全匹配的字符串
    // 这时,i指向haystack中与needle匹配的字符串的最后一个字符
    if (j === needle.length) return i - needle.length + 1; 
  }
  
  // 6. 如果haystack和needle匹配不上,返回-1
  return -1;
};
  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值