KMP演算法(基於程式碼隨想錄)的隨筆

十几里路發表於2024-03-09

KMP

KMP的主要思想是當出現字串不匹配時,可以知道一部分之前已經匹配的文字內容,可以利用這些資訊避免從頭再去做匹配了。

字首表:起始位置到下標i之前(包括i)的子串中,有多大長度的相同字首字尾。

那麼使用KMP可以解決兩類經典問題:

  1. 匹配問題:28. 實現 strStr()(opens new window)
  2. 重複子串問題:459.重複的子字串(opens new window)

再一次強調了什麼是字首,什麼是字尾,什麼又是最長相等前字尾。

最長公共前字尾

字首:指不包含最後一個字元的所有以第一個字元開頭的連續子串。

字尾:指不包含第一個字元的所有以最後一個字元結尾的連續子串。

然後針對字首表到底要不要減一,這其實是不同KMP實現的方式,我們在KMP精講 (opens new window)中針對之前兩個問題,分別給出了兩個不同版本的的KMP實現。

其中主要理解j=next[x]這一步最為關鍵!

時間複雜度分析

其中n為文字串長度,m為模式串長度,因為在匹配的過程中,根據字首表不斷調整匹配的位置,可以看出匹配的過程是O(n),之前還要單獨生成next陣列,時間複雜度是O(m)。所以整個KMP演算法的時間複雜度是O(n+m)的。

暴力的解法顯而易見是O(n × m),所以KMP在字串匹配中極大地提高了搜尋的效率。

字首表不減一來構建next陣列,程式碼如下:

    void getNext(int* next, const string& s) {
        int j = 0;
        next[0] = 0;
        for(int i = 1; i < s.size(); i++) {
            while (j > 0 && s[i] != s[j]) { // j要保證大於0,因為下面有取j-1作為陣列下標的操作
                j = next[j - 1]; // 注意這裡,是要找前一位的對應的回退位置了
            }
            if (s[i] == s[j]) {
                j++;
            }
            next[i] = j;
        }
    }

構建完next陣列後,對字串匹配的實現程式碼如下:

class Solution {
public:
    void getNext(int* next, const string& s) {
        int j = 0;
        next[0] = 0;
        for(int i = 1; i < s.size(); i++) {
            while (j > 0 && s[i] != s[j]) {
                j = next[j - 1];
            }
            if (s[i] == s[j]) {
                j++;
            }
            next[i] = j;
        }
    }
    int strStr(string haystack, string needle) {
        if (needle.size() == 0) {
            return 0;
        }
        int next[needle.size()];
        getNext(next, needle);
        int j = 0;
        for (int i = 0; i < haystack.size(); i++) {
            while(j > 0 && haystack[i] != needle[j]) {
                j = next[j - 1];
            }
            if (haystack[i] == needle[j]) {
                j++;
            }
            if (j == needle.size() ) {
                return (i - needle.size() + 1);
            }
        }
        return -1;
    }
};
  • 時間複雜度: O(n + m)
  • 空間複雜度: O(m)

相關文章