这道题是之前参加一个LeeCode的一个比赛遇到的题,当时采用暴力解法,但是写的也不是很完美,有点像392求子序列,然后打开题解,KMP算法,好的,用时两天,终于学明白了。(这道题很常见,看见的友友们记得耐心学完哦)。
正文开始。
其实匹配过程就相当于一个长的尺子,一个短的尺子,短尺子向右移动,什么时候能与长尺子的子串匹配上。
这就是匹配成功的状态。
我们来思考,以我们单纯又清澈的想法怎么做?
1.当主串与模式串的字符匹配上了,那就是可以进行下一个值的匹配了,i++,j++
当下一个值不匹配时,
我们知道主串以a开头看来是不行,那就下一个,以b开头,模式串从头开始
这时发现还是不匹配,那就主串继续往下,i++.
...
重复这个过程,可以看出主串是每次加1个,模式串是不匹配就立马回到第一位,像往前爬一样慢,所以我们接下来的优化就针对这两方面,主串优化+模式串优化。
(暴力算法中每次遇到不匹配的值,模式串都回到最初的起点,要是能够使模式串往回走的时候,不是到起点,而是到半路,在半路继续往后查找,是不是就快了。)
在此附上暴力算法代码
var strStr = function(haystack, needle) {
var a = haystack.length;
var b = needle.length;
for(var i = 0;i+b<=a;i++){
var flag = true;
for(var j = 0;j<=b-1;j++){
if(haystack[i+j] !== needle[j]){
flag = false;
break;
}
}
if(flag){
return i
}
}
return -1
};
接下来的部分,你就给我学,学完你会有种恍然大明白的感觉。
知识点一
1.什么是前缀?怎么求前缀?
如果字符串A和B,存在A=BS,其中S是任意的非空字符串,那就称B为A的前缀。
举例: ”Harry”的前缀包括{”H”, ”Ha”, ”Har”, ”Harr”},
2.什么是后缀?怎么求后缀?
定义后缀A=SB, 其中S是任意的非空字符串,那就称B为A的后缀.
举例:”Potter”的后缀包括{”otter”, ”tter”, ”ter”, ”er”, ”r”}
注意:字符串本身并不能算作前缀和后缀。
知识点二
1.next数组是怎么构成的?
next数组中的值是字符串的前缀集合与后缀集合的交集中最长元素的长度。
举例:字符串“abababca”.
2.next数组有什么用?为什么是这个规则?跟做题目有什么关系?
答:记住这个规则,在本文后面会解释,继续往下学
3.next数组的逻辑是怎么实现的?怎么使用代码实现?
继续....
这段话很重要:
目标串“ababa”具有相同的前缀和后缀“ab”.
比如j按暴力解法,走到最开始,那就是按照前缀往后走,但其实由于前缀=后缀,可以将pattern的后缀替换为target的前缀,
我们知道i一直在++,不会变,使j移动到target前缀的后一个索引即可(j=next[j-1])!!!
以上就是KMP算法的核心,求next数组
next数组代码实现:
let next = new Array(m);//next数组
next[0] = 0;
//i是一直++的
for (let i = 1, j = 0; i < m; i++){
//循环进行,不相等就向前
while (j && s2[i] !== s2[j]) {
j = next[j - 1];
}
if (s2[i] === s2[j]) ++j;//匹配 j右移
//不管匹配与否,都将j值赋给next数组
next[i] = j;
}
好了,现在已经掌握KMP算法的核心了,继续...
我们最终得到next数组,怎么去使用它呢?
其实我们刚刚的操作,就是在执行字符串匹配的过程,所以next数组代表:当遇到不匹配的字符时,我们应该去next数组中,找到j应该跳到什么位置。
//匹配
for (let i = 0, j = 0; i < n; i++){
while (j && s1[i] !== s2[j]) {// 失配 左移
j = next[j - 1];
}
if (s1[i] === s2[j]) ++j;// 匹配 j + 1
if (j === m) return i - m + 1;
}
现在来解答这个问题。next数组有什么用?为什么是这个规则?跟做题目有什么关系?
1.next数组是为了:当遇到不匹配的字符时,我们应该去next数组中,找到j应该跳到什么位置。
2.为什么是这个规则:规则就是匹配时+1,不匹配时,j回到next[j-1]索引的位置。为了回到距离当 前位置较近的位置,而去寻找能够使后缀与前缀重叠
3.跟题目有什么关系?上面我们说过 主串优化与模式串优化,next数组是模式串优化,使用相同思想再对主串进行优化。
var strStr = function(haystack, needle) {
const n = haystack.length;//匹配串
const m = needle.length;//模式串
if (!m) return 0;//模式串为空
let next = new Array(m);//next数组
next[0] = 0;
//模式串优化
for (let i = 1, j = 0; i < m; i++){
while (j && s2[i] !== s2[j]) {//不匹配,左移
j = next[j - 1];
}
if (s2[i] === s2[j]) ++j;//匹配 j右移
next[i] = j;
}
//匹配(主串优化)
for (let i = 0, j = 0; i < n; i++){
while (j && s1[i] !== s2[j]) {// 失配 左移
j = next[j - 1];
}
if (s1[i] === s2[j]) ++j;// 匹配 j + 1
if (j === m) return i - m + 1;
}
return - 1;
}
好了以上就是KMP算法的JS实现过程了,欢迎小伙伴一起学习讨论呀!
最后的最后,附上我看过的觉得还不错的文章:
如何更好地理解和掌握 KMP 算法? - 知乎 (第一篇、林小浩、宫水三叶)