KMP算法介紹(力扣28.實(shí)現(xiàn)strStr())

學(xué)習(xí)筆記三 KMP算法介紹

本題來自:力扣28.實(shí)現(xiàn)strStr()

題目描述

簡單來說,題目要求是從一個字符串 haystack 中找到另一個字符串 needle 出現(xiàn)的第一個位置。如果按暴力解法,題目解答很容易想到是 O(m * n) 的時間復(fù)雜度。那么,有沒有一種解法,它的時間復(fù)雜度可以降到 O(m + n) 呢?

有!這就是我們接下來要介紹的 KMP 算法

KMP的主要思想

  • KMP的主要思想是:當(dāng)出現(xiàn)字符串不匹配時,可以知道一部分之前已經(jīng)匹配的文本內(nèi)容,避免從頭再去做匹配了。這也是我們能將時間復(fù)雜度降低的本質(zhì)。

KMP實(shí)現(xiàn)的關(guān)鍵步驟

一、構(gòu)建 next 數(shù)組

先來解釋幾個名詞:前綴,后綴,最長公共前后綴,前綴表

前綴:不包含最后一個字符的,并以第一個字符為開頭的所有子字符串
后綴:不包含第一個字符的,并以最后一個字符為結(jié)尾的所有子字符串

最長公共前后綴:對于一個字符而言,最長的相同的前綴后綴
前綴表:前綴表是記錄下標(biāo) i 之前(包括 i)的字符串中,有多大長度的最長公共前后綴

而我們要構(gòu)建的 next 數(shù)組,就是所謂的前綴表。

KMP 算法實(shí)現(xiàn)的第一個難點(diǎn)便是 next 數(shù)組的構(gòu)建求解,即 對于一個子串,如何去求它的最長公共前后綴?

構(gòu)造 next 數(shù)組其實(shí)就是計(jì)算短串的前綴表的過程,其關(guān)鍵步驟主要有如下三步:

  • 初始化
  • 處理前后綴不相同的情況
  • 處理前后綴相同的情況

1,初始化

首先我們定義一個函數(shù) getNext 去構(gòu)建 next 數(shù)組,函數(shù)參數(shù)為指向 next 數(shù)組的指針,和一個字符串。

void getNext(int* next, string needle)   //利用函數(shù)封裝,使主函數(shù)邏輯更加簡潔

由于我們要比較一個字符串的前綴后綴,那就必須有兩個指針分別在字符串的開頭和結(jié)尾。而對于 next 而言,第一位只有一個字母的情況前綴后綴都是 0 ,可以直接初始化。

int left = 0;
next[0] = left;
for (int right = 1; right < needle.size(); right++) {
        
}

2,后綴不相同

如果在中間某個位置發(fā)現(xiàn) right 與 left 的值不相同,就需要不斷進(jìn)行回溯。

由于 next 數(shù)組保留著前后綴相同的位置,那么回溯的位置便是 next 數(shù)組中 left 的前一個位置 left - 1 。如果回溯后的 left 仍然不等于 right 位置的字符,那就繼續(xù)回溯,直到 left 到達(dá)字符串左邊界。但是為了防止 left - 1 溢出數(shù)組邊界,left = 1 時便就是邊界了,因此判斷條件為 left > 0。

while (left > 0 && needle[left] != needle[right]) {
    left = next[left - 1];
}

3,后綴相同

如果 left 和 right 的后綴相同,那么說明找到了相同的前后綴。left,right 遞增,同時把 left 的值賦給 next 數(shù)組。

if (needle[left] == needle[right]) {
    left++;
}
next[right] = left;

4,完整代碼

因此,構(gòu)建 next 數(shù)組完整代碼如下:(時間復(fù)雜度O(n))

    void getNext(int* next, string needle) {
        int left = 0;
        next[0] = left;
        for (int right = 1; right < needle.size(); right++) {
            while (left > 0 && needle[left] != needle[right]) {
                left = next[left - 1];
            }
            if (needle[left] == needle[right]) {
                left++;
            }
            next[right] = left;
        }
    }

二、利用 next 數(shù)組進(jìn)行匹配

那么有同學(xué)就要問了,得到這個前綴表,到底對字符串匹配過程有什么幫助呢?
別急,我們來看一個小例子。

  • 例:如果我們要從 aabaabaafa 中尋找 aabaaf 第一次出現(xiàn)的位置。

1,暴力解法:兩個字符串均從第一個位置開始比較,比較到第6位發(fā)現(xiàn) b 不等于 f ,比較失敗。長串走到第二個位置;短串回到第一個位置,繼續(xù)比較直到長串走完。( 時間復(fù)雜度O(m * n))
2,KMP算法:兩個字符串均從第一個位置開始比較,比較到第6位發(fā)現(xiàn) b 不等于 f,比較失敗。長串位置不變;短串,調(diào)用 next 數(shù)組,找到 next 數(shù)組第5位的數(shù)字,并作為位置傳遞給短串,這時候短串到第3位 b 字符,和長串匹配,比較繼續(xù)。(時間復(fù)雜度O(m))

匹配錯誤時

只是再解釋一下匹配錯誤時的情況,第6位 f 匹配失敗,這時候第5位 next 數(shù)組的值為2,這說明此時第4、5位和第1、2位相同,又因?yàn)榈?、5位已經(jīng)和長串匹配成功,這說明第1、2位也可以匹配成功,因此直接從第三位,也就是數(shù)組下標(biāo)為2的地方開始短串匹配即可。

匹配成功時

長短串位置均正常遞增即可,只是需要增加結(jié)束條件,即短串位置到達(dá)短串右端點(diǎn),這說明長串已經(jīng)有位置可以將短串完全匹配,循環(huán)結(jié)束。

我們可以發(fā)現(xiàn),匹配過程和 next 數(shù)組生成過程代碼幾乎完全一樣,因此代碼的思路模版可以進(jìn)行記憶背誦。

  • 完整代碼:
        int n = 0;
        for (int h = 0; h < haystack.size(); h++) {
            while (n > 0 && haystack[h] != needle[n]) {
                n = next[n - 1];
            }
            if (haystack[h] == needle[n]) {
                ++n;
            }
            if (n == needle.size()) {
                return h - needle.size() + 1;
            }
        }
        return -1;

KMP完整代碼

class Solution {
public:
    void getNext(int* next, string needle) {
        int left = 0;
        next[0] = left;
        for (int right = 1; right < needle.size(); right++) {
            while (left > 0 && needle[left] != needle[right]) {
                left = next[left - 1];
            }
            if (needle[left] == needle[right]) {
                left++;
            }
            next[right] = left;
        }
    }

    int strStr(string haystack, string needle) {
        if (needle.size() == 0) {
            return 0;
        }
        int next[needle.size()];
        getNext(next, needle);
        int n = 0;
        for (int h = 0; h < haystack.size(); h++) {
            while (n > 0 && haystack[h] != needle[n]) {
                n = next[n - 1];
            }
            if (haystack[h] == needle[n]) {
                ++n;
            }
            if (n == needle.size()) {
                return h - needle.size() + 1;
            }
        }
        return -1;
    }
};
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容