介绍
KMP用于解决字符串匹配问题
用暴力匹配法,会产生许多回溯,每次只移动一位,若匹配不成功,就在移动一位。试想采用这样的方法,若一个给定字符串需要去进行匹配,前面的字符都匹配成功,只有最后一位匹配失败,那前面的努力不就都浪费了嘛
原理及相关概念
前缀和后缀
前缀:包含首字母,不包含尾字母的所有子串
后缀:包含尾字母,不包含首字母的所有子串
比如字符串abc,前缀就是a,ab,后缀就是c,bc
最长相等前后缀
比如字符串abac,其中a最长相等前后缀为0,ab最长相等前后缀为0,aba最长相等前后缀为1,abac最长相等前后缀为0;
这时就产生了一个前缀表0010,通过这个前缀表就可以避免一些重复匹配的过程,它的原理是利用最长相等前后缀的特性,当文本串和模式串都匹配了一些字符后,这时遇到不相等字符,模式串回退到最长相等前缀,文本串利用最长相等后缀,这样在进行比较
实现(JS)
力扣 28. 找出字符串中第一个匹配项的下标
// 计算字符串s的前缀表next数组
function getNext(next, s) {
// j表示前缀末尾位置,i表示后缀末尾位置
next[0] = 0
let j = 0
for (let i = 1; i < s.length; i++) {
// 当前后缀末位位置字符不相等时,j要回退
while (j > 0 && s[i] !== s[j]) {
j = next[j - 1]
}
// 当前后缀末位位置字符相等时,i和j都要向前移动
if (s[i] === s[j]) {
j++
}
// j当前的位置就表示最长相等前后缀
next[i] = j
}
}
// haystack 文本串
// needle 模式串
var strStr = function (haystack, needle) {
// 得到前缀表
let next = []
getNext(next, needle)
// 遍历needle
let index = 0
for (let i = 0; i < haystack.length; i++) {
// 当两个字符串匹配到有不相等的字符时,指向模式串的指针需要根据其前缀表进行回退(注意是循环,即要回退到退不动或者出现两个字符串匹配到有相等的字符)
while (index > 0 && haystack[i] !== needle[index]) {
index = next[index - 1]
}
// 当两个字符串匹配到有相等的字符时,i和index向前移动
if (haystack[i] === needle[index]) {
index++
}
// 模式串匹配结束,return下标
if (index === needle.length) {
return i - index + 1
}
}
return -1
}
时间复杂度分析
暴力解法O(n*m),KMP算法O(n+m)
其中n为文本串长度,m为模式串长度