[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)。

相關文章
相關標籤/搜索