[leetcode]kmp算法js版

题目:给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。若是不存在,则返回 -1。

示例 1:算法

输入: haystack = "hello", needle = "ll"
输出: 2
复制代码

示例 2:数组

输入: haystack = "aaaaa", needle = "bba"
输出: -1
复制代码

思路1:暴力匹配bash

假设haystack="abcabcx",needle="abcabx",m为haystack的长度,n为needle的长度, i为字符串haystack的索引,j为字符串needle的索引,依次比较haystack[i]和needle[j],以下图:ui

当发现haystack[i]和needle[j]不相等时,以下图:

回溯两个指针,从新比较,直到needle彻底匹配上或haystack字符串循环结束也没有匹配上为止,以下图。spa

暴力匹配方法须要不停的回溯两个指针,时间复杂度为O(m*n)。3d

思路2:kmp算法指针

咱们先观察一下上面第一次不匹配时的状况,为了清晰标注了一下颜色:code

不匹配字符前面的ab(绿色)是已知匹配上的,刚好needle最前面的两个字符也是ab(红色),cdn

这提示咱们能够设法利用红色ab和绿色ab相等来减小匹配的次数。咱们能够把j直接移动到红色ab的下一个字符,i保持不动,继续进行匹配,以下图:blog

继续进行匹配,若是再遇到不匹配字符,重复上述步骤,直到needle彻底匹配上,或者haystack字符串循环结束也没有匹配上为止,以下图:

这样不用回溯i,大大节省了效率。

把上面的步骤用代码实现:

var strStr = function(haystack, needle) {
    var m = haystack.length, n = needle.length;
    if(!n) return 0;
    var next = kmpProcess(needle);
    for(var i = 0, j = 0; i < m;) {
        if(haystack[i] == needle[j]) { // 字符匹配,i和j都向前一步
            i++, j++;
        }
        if(j == n) return i - j; // needle彻底匹配上,返回匹配位置
        if(haystack[i] != needle[j]) { // 字符不匹配
            if(j > 0) {
                // TODO 如何重置j呢?
            } else {
                i++;
            }
            
        }
        
    }
    return -1;
};
复制代码

那么字符不匹配时,怎样让程序知道应该把j重置在什么地方呢?咱们先来观察一下第一次不匹配时的状况:

此时在不匹配字符x前,needle的子串是:abcab,经过肉眼观察,前缀ab和后缀ab相等,字符串"ab"的长度是2,这时咱们要把j重置到needle数组下标为2的地方(数组下标从0开始)。能够按照这个思路把needle中每一个字符不匹配时,重置j的位置对应的记录到一个数组next里,咱们接下来要作的就是求出next数组。

接下来咱们开始计算next数组。最差的状况就是j重置到0,先把数组用0填充。

假设x是遍历needle的索引,y是neddle数组从0开始的索引,也是j将要重置的位置(即存入next数组的值)。由于needle的第一个字符没有先后缀,因此next[0]永远是0,因此x从1开始。计算next的过程以下:

  1. needle[x] != neddle[y],因为y=0,因此next[x]=0,而且x向前一步,以下图:

2.needle[x] != neddle[y],因为y=0,因此next[x]=0,而且x向前一步,以下图:

  1. needle[x] == neddle[y],以下图:

    此时y要先前进一步,next[x]=前进后的y(此时为1),而且x也向前一步,此时next[x]即next[3]=1以下图:

  2. needle[x] == neddle[y],以下图:

    此时y要先前进一步,next[x]=前进后的y(此时为2),而且x也向前一步,此时next[x]即next[4]=2以下图:

5.needle[x] != neddle[y],因为y != 0,说明以前有匹配上的字符串,此时须要移动y至next[y-1],即y=0,再去比较needle[x] 与 neddle[y],注意,y !=0 或者needle[x] != neddle[y]时,x不能前进,要保持在原地,以下图:

根据needle[x] 与neddle[y]相等和不相等的状况,反复上述步骤,直到needle遍历完为止。此时needle[x] !=neddle[y]且y=0,因此next[x]=0。 这时next数组也被填充完了,以下图。

最后获得next数组为:[0, 0, 0, 1, 2, 0]

把上面两步用代码实现:

var strStr = function(haystack, needle) {
    var m = haystack.length, n = needle.length;
    if(!n) return 0;
    var next = kmpProcess(needle);
    for(var i = 0, j = 0; i < m;) {
        if(haystack[i] == needle[j]) { // 字符匹配,i和j都向前一步
            i++, j++;
        }
        if(j == n) return i - j; // needle彻底匹配上,返回匹配位置
        if(haystack[i] != needle[j]) { // 字符不匹配
            if(j > 0) {
                j = next[j - 1]; // 重置j
            } else {
                i++;
            }
            
        }
        
    }
    return -1;
};

var kmpProcess = function(needle) {
    var y = 0;
    var x = 1;
    var next = new Array(needle.length).fill(0);
    while (x < needle.length) {
        if (needle[x] == needle[y]) {
            y++;
            next[x] = y;
            x++;
        } else if (y > 0) {
            y = next[y - 1];
        } else {
            next[x] = 0;
            x++;
        }
    }
    return next;
}

console.log(strStr('abcabcabya', 'abcaby')); // 3
复制代码

kmpProcess的时间复杂度是O(n),不须要回溯haystack的索引i,整个算法的时间复杂度为O(m+n)。

相关文章
相关标签/搜索