【前端er每日算法】终于写了一回KMP

84 阅读1分钟

题目

28. 找出字符串中第一个匹配项的下标

这个问题是经典的字符串匹配算法,鼎鼎大名的KMP,之前一直没弄懂,今天又看了一遍,比之前理解更深一点了,把代码也写了一遍。

思路

算法的逻辑是:

  1. 求解next数组,next数组是模式串的相同的前后子串的最大长度。
  2. 匹配逻辑根据next数组进行回退,减少比较次数。当有字符不匹配时,根据next[j-1]的值不断更新,直到遇到相等的值或者j又返回了0,返回0就相当于从头开始匹配了。

为啥可以根据next数组来回退?因为这里记录的是前i个元素(包含i)得最长公共前后缀,如果遇到不相等的字符后,说明前面是匹配的,所以可以跳过和匹配字符相同的那几个字符,这个就从next中获取,相同的前后缀就知道当前不匹配的字符前面有哪几个相同,这样的话跳过前缀的相同字符,从不同的字符开始,这样减少比较次数。

/**
 * @param {string} haystack
 * @param {string} needle
 * @return {number}
 */

function getNext(needle) {
    let j = 0;
    let next = [0];
    for (let i = 1; i < needle.length; i++) {
        while (j > 0 && needle[i] !== needle[j]) {
            j = next[j - 1];
        }
        if (needle[i] === needle[j]) {
            j++;
        }
        next[i] = j;
    }
    return next;
}

var strStr = function(haystack, needle) {
    const next = getNext(needle);
    let j = 0;
    for (let i = 0; i < haystack.length; i++) {
        // 先处理不相同,否则j++后会不一样
        while (j > 0 && haystack[i] !== needle[j]) {
            j = next[j - 1];
        }
        if (haystack[i] === needle[j]) {
            j++;
        } 
        if (j === needle.length) { // 起始位置为i的位置减去needle的长度
            return i - needle.length + 1;
        }
    }
    return -1;
};