KMP 算法

来自:程序员代码面试指南:IT名企算法与数据结构题目最优解(第2版)左程云 P542

28. 实现 strStr()

如果字符串str中含有子串match,则返回matchstr中的开始位置,不含有则返回-1

KMP算法是如何快速解决字符串匹配问题的?

  1. 生成match字符串nextArr数组

nextArr[i]的含义

match[i]之前的字符串match[0..i-1]

match[i-1]结尾的后缀子串(不能包含match[0])与必须

match[0]开头的前缀子串(不能包含match[i-1])

最大匹配长度是多少,这个长度就是 nextArr[i]的值

🌰

match="abc1abc1"字符串,nextArr[7]的值该是多少呢?

当索引为 7 时,match[7] = 1 前缀为 match[0...5] = abc1ab (不能包含6) ,后缀为 match[1...6]bc1abc(不能包含0)

前缀:   	abc1ab
后缀:	 bc1abc

可以看到,前缀子串与后缀字串abc匹配,所以其最大匹配长度为 3

nextArr[7]

  1. 假设从str[i]字符出发时,匹配到j位置的字符发现与match中的字符不一致。

也就是说,str[i]match[0]一样,并且从这个位置开始一直可以匹配,即str[i..j-1]match[0..j-i-1]一样

直到发现str[j] != match[j-i],匹配停止,如下图
在这里插入图片描述
因为现在已经有了match字符串nextArr数组nextArr[j-i]的值表示match[0..j-i-1]这一段字符串前缀与后缀的最长匹配

假设前缀是a区域这一段,后缀是b区域这一段,再假设a区域的下一个字符为match[k]
在这里插入图片描述

那么下一次的匹配检查不再像普通解法那样退回到 str[i+1]重新开始与 match[0]的匹配过程,而是直接让str[j]match[k]进行匹配检查

在这里插入图片描述

如何快速得到match字符串的nextArr数组?

因为是左到右依次求解 nextArr,所以在求解 nextArr[i]时,nextArr[0..i-1]的值都已经求出

假设match[i]字符A字符match[i-1]B字符

通过nextArr[i-1]的值可知B字符前的字符串的最长前缀与后缀匹配区域,l区域为最长匹配的前缀子串,k区域为最长匹配的后缀子串,字符Cl区域之后的字符,然后看字符C与字符B是否相等

在这里插入图片描述
如果字符C与字符B相等,那么A字符之前的字符串的最长前缀与后缀匹配区域就可以确定,前缀子串为l区域+C字符,后缀子串为k区域+B字符,即nextArr[i]=nextArr[i-1]+1

如果字符C与字符B不相等,就看字符C之前的前缀和后缀匹配情况,假设字符C是第cn个字符(match[cn]),那么nextArr[cn]就是其最长前缀和后缀匹配的长度
在这里插入图片描述

m区域和n区域分别是字符C之前的字符串最长匹配的后缀与前缀区域,这是通过 nextArr[cn]的值确定的,当然两个区域是相等的,m’区域为 k区域最右的区域且长度与m区域一样,因为k区域和l区域是相等的,所以m区域和m’区域也相等,字符D为n区域之后的一个字符,接下来比较字符D是否与字符B相等

1)如果相等,A字符之前的字符串最长前缀与后缀匹配区域就可以确定,前缀子串为n区域+D字符,后缀子串为m’区域+B字符,则令nextArr[i]=nextArr[cn]+1

2)如果不等,继续往前跳到字符D,之后的过程与跳到字符C类似,一直进行这样的跳过程,跳的每一步都会有一个新的字符和B比较(就像C字符和D字符一样),只要有相等的情况,nextArr[i]的值就能确定。

如果向前跳到最左位置(即match[0]的位置),此时nextArr[0] == -1,说明字符A之前的字符串不存在前缀和后缀匹配的情况,则令nextArr[i]=0。用这种不断向前跳的方式可以算出正确的nextArr[i]值的原因还是因为每跳到一个位置cn,nextArr[cn]的意义就表示它之前字符串的最大匹配长度

var strStr = function(haystack, needle) {
  if (!needle) return 0
  return getIndexOf(haystack, needle)
};

const getIndexOf = (s, m) => {
  if (!s || !m || s.length < m.length) return -1
  let si = mi = 0
  const next = getNextArray(m)
  while (si < s.length && mi < m.length) {
    if (s[si] == m[mi]) {	// 匹配
      si++
      mi++
    } else if (next[mi] == -1) {	
      si++
    } else {	// mi 继续与之前不匹配的 si 匹配
      mi = next[mi]
    }
  }
  return mi == m.length ? si - mi : -1
}

// 得到 match 字符串的 nextArr 数组
const getNextArray = m => {
  if (m.length == 1) return [-1]
  const next = new Array(m.length)
  next[0] = -1
  next[1] = 0
  let pos = 2
  let cn = 0
  while (pos < next.length) {
    if (m[pos - 1] == m[cn]) {
      next[pos++] = ++cn
    } else if (cn > 0) {
      cn = next[cn]
    } else {
      next[pos++] = 0
    }
  }
  return next
}

js 中 可以直接使用 indexOf 方法,得到和上面相同的结果

var strStr = function(haystack, needle) {
    return haystack.indexOf(needle)
};

Java 版本

在这里插入图片描述

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值