KMP
KMP的主要思想是當出現字串不匹配時,可以知道一部分之前已經匹配的文字內容,可以利用這些資訊避免從頭再去做匹配了。
字首表:起始位置到下標i之前(包括i)的子串中,有多大長度的相同字首字尾。
那麼使用KMP可以解決兩類經典問題:
- 匹配問題:28. 實現 strStr()(opens new window)
- 重複子串問題: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)