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的子字符串里:
- 只要(while)前缀最后一个字符不匹配后缀最后一个字符,就将j回退到可能匹配的位置,再更新next数组;
- 如果(if)前缀最后一个字符匹配后缀最后一个字符,就将j++,再更新next数组
- 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;
};