【第5期】算法精選-你應該知道的KMP算法

本期講講KMP算法,也就是江湖俗稱的看毛片算法。前端

這個算法其實在面試中出現的機率仍是蠻大的,無論是校招仍是社招,甚至在考研中也遇到過,並且KMP算法也比較難理解,因此頗有必要研究一下。面試

KMP要解決的問題

先說一下這個算法出現的背景,也就是解決什麼問題。每一個算法和框架都有它出現的緣由和要解決的問題,不少時候你不會一個技術,並非你笨,而是你沒有找到它的使用場景或者是沒搞明白它要解決的問題而已。因此,瞭解它要解決的問題,是學習的重中之重。算法

首先看一個例子:數組

字符串str="abcabcabcde",字符串match="abcabcde"

若是str包含子串match,返回match在str中的起始位置。框架

這個問題其實可使用暴力來破解,以下圖:學習

從str[i]開始(i初始等於0),每次只要遇到了match和str不匹配的狀況,str回退到str[i+1],再繼續str[i+1]和match[0]依次對比,久而久之,直到match徹底匹配出來或者str遍歷完畢爲止,這樣作的時間複雜度是O(M*N),M是str的長度,N是match的長度。spa

那麼有沒有種方法能夠不這麼笨拙的解決字符串匹配的問題呢?3d

KMP算法就是解決match和str在匹配過程當中不停的作回退的問題的。簡單一句話,KMP算法是作字符串高效匹配的算法code

KMP算法是如何作的

說KMP的解法以前,先看看一個聰明的人類是如何解決這個問題的:blog

這樣作的緣由是不必一次性退回從match[0]和str[1]開始比較,由於能夠看出來綠色框內的match[0,1,2]和str[3,4,5]是重合的,重合的這部分徹底能夠複用,沒有必要再從match[0]和str[1]開始重複比較;

具體的過程能夠模擬抽象成下圖:

因此問題轉化成了:

每次str[j]和match[j]不相同時,在match[0...j-1]這段字符串中,以match[j-1]結尾的後綴子串(不能包含match[0])和以match[0]開頭的前綴子串(不能包含match[j-1])的最大匹配長度是多少?只要求出這個最大匹配長度,就能在str[j]和match[j]不相同時,知道把match滑動到什麼位置了!

KMP算法的重點就是維護一個數組,保存match中每一個字符在不匹配時,match應該滑動到什麼位置,這個數組起名叫next。

如何構造next數組

那麼如何構造next數組呢?

首先對於match[0]來講,它前面沒有字符,因此next[0]規定爲-1;對於match[1]來講,由於next數組規定計算的時候子串後綴不能包含第一個字符,因此next[1]=0;

說完特殊狀況,再說說常規狀況,好比如今假設match[i]是A字符,match[i-1]是B字符,能夠經過next[i-1]獲得B字符前的字符串最長前綴與後綴的匹配區域,如今假設L爲最長前綴子串,K爲最長後綴子串,C爲最長前綴子串以後的一位字符,如今只須要比較C和B便可

  1. 若是字符B等於字符C,那麼A字符以前的字符串的最長前綴與後綴匹配區域就能夠肯定了,最長匹配前綴子串爲L+C,最長匹配後綴子串爲K+B,next[i] = next[i-1]+1;
  2. 若是字符B不等於字符C,就看C字符以前的前綴後綴匹配問題。假設C的位置是match[cn],那麼能夠經過next[cn]獲得字符C前的字符串的最長匹配前綴是M,最長匹配後綴是N,再假設M後面一位字符是D,又由於L=K,因此同等位置的p=N;接下來就是比較一下B和D是否相同,若是B=D,那麼A字符以前的字符串最長匹配前綴子串是M+D,最長匹配後綴子串是P+B,若是不相同的話,照着以前說的思路繼續往前跳,直到跳到match[0]

代碼以下

/**
 * @param {string} haystack
 * @param {string} needle
 * @return {number}
 */
var strStr = function(str, match) {
    if(str === null || match === null || match.length > str.length)
        return -1;
    if(str === "")
        return 0;
    
    let index1 = 0;
    let index2 = 0;
    let nextArr = getNext(match);
    console.log(nextArr);
    while(index1 < str.length && index2 < match.length) {
        if(str[index1] === match[index2]) {
            index1++;
            index2++;
        } else if(nextArr[index2] === -1) {
            index1++;   
        } else {
            index2 = nextArr[index2];
        }
    }
    
    return index2 === match.length ? index1 - index2 : -1;
};

var getNext = function(match) {
    let next = [-1, 0];
    
    let cn = 0;
    let index = 2;
    
    while(index < match.length) {
        if(match[index-1] === match[cn]) {
            cn++;
            next[index] = cn;
            index++;
        } 
        else if(cn > 0) {
            cn = next[cn];
        } 
        else {
            next[index] = 0;
            index++;
        }
    }
    
    return next;
}

感興趣能夠關注個人公衆號——前端亞古獸

相關文章
相關標籤/搜索