面試官問:你知道什麼是KMP算法嗎?說說你對KMP算法的理解。
答:KMP算法是用來進行字符串匹配查找的,比如在字符串1中查找是否包含字符串2。核心是先求出Next數組。什麼是next數組?我的理解是:
next數組表示的是待查找的字符串的最大公共前後綴中的公共前綴的最後一個字符的下標,知道這個下標,就可以知道當匹配目標字符串出錯時,目標字符串的指針怎麼回退,而查找段落的指針不用回退,這樣遍歷一遍查找段落,就可以知道是否存在目標字符串,時間複雜度爲O(n)
例如,求目標字符串ababc的next數組,下標從0開始。
比如next[2],2位置爲a,則next[2]表示0位置的a(因爲aba的最長公共前綴爲a),next[2] =0。
比如next[3],3位置爲b,則next[3]表示1位置的b(因爲abab的最長公共前綴爲ab), next[3] = 1。
比如next[4],由於4位置爲c,而ababc沒有公共前後綴,則next[4]=-1。
那麼什麼是最長公共前後綴,爲什麼需要它和next數組?這裏我解釋下它的原理:
- 比如需查找字符串"ababc",子串ab最長公共前後綴長度爲0(因爲a和b不等),子串aba最長公共前後綴長度爲1(a的長度,首和尾都有a), 字串abab最長公共前後綴長度爲2(ab的長度,首和尾都有ab),ababc最長公共前後綴長度爲0(首和尾不同)。
- 在子串1“abababc”中查找子串2"ababc",對照看下圖:當我們匹配到第五位時,發現a和c不相等,如果沒有這個next數組,我們需要重新比對子串1的第2位(指針從第5位回到第2位)和子串2的第1位。有了這個next數組,我們知道錯誤匹配字母c前面的abab最大公共前後綴長度爲2,所以我們可以直接右移子串2兩位,比對子串1的還是第五位和子串2的第3位。這樣的匹配算法,字串1中的指針不用回退,等於只要遍歷一遍字串1就可以查到是否包含字串2,時間複雜度位O(n)
- next數組求法代碼爲:
public void getNext(String needle,int next[]){
//規定長度爲1的字符串沒有最長公共前後綴
next[0]=-1;
char []arr=needle.toCharArray();
int strLen=arr.length;
for(int i=1;i<strLen;i++){
int t=next[i-1];
// 下面的while循環表示arr[t+1]!= arr[i],不等時指針就t=next[t]回退,爲什麼需要回退?
//比如abcaba中前綴abc的c和後綴aba的a不等,但並不代表沒有公共前後綴了,如前綴的a和後綴的a相等,
//所以需要回退,t>=0 表示指針還沒回退到起點,如果回退到起點了都不等,那隻能說明沒有公共前後綴了。
while(arr[t+1]!=arr[i]&&t>=0)
t=next[t];
//判斷是因相等退出循環,還是到了起點都不等而退出循環
if(arr[t+1]==arr[i])
next[i]=t+1;
else
next[i]=-1;
}
}
求到了next數組後,只要遍歷一遍子串1就可以了,如果出現不匹配,則回退子串2的指針。如果字串2已經回退到起點了,則移動字串1到下一個位置繼續匹配即可。
實踐運用KMP算法:
這是一道LeetCode的面試題目,第28題,題目爲:
實現 strStr() 函數。
給定一個 haystack 字符串和一個 needle 字符串,在 haystack 字符串中找出 needle 字符串出現的第一個位置 (從0開始)。如果不存在,則返回 -1。
示例 1:
輸入: haystack = "hello", needle = "ll"
輸出: 2
一種取巧的方法是java中String的一個函數indexOf()可以直接得出結果。
這道題完整的解答代碼:
class Solution {
public void getNext(String needle,int next[]){
next[0]=-1;
char []arr=needle.toCharArray();
int strLen=arr.length;
for(int i=1;i<strLen;i++){
int t=next[i-1];
while(arr[t+1]!=arr[i]&&t>=0)
t=next[t];
if(arr[t+1]==arr[i])
next[i]=t+1;
else
next[i]=-1;
}
}
public int strStr(String haystack, String needle) {
int len=needle.length();
if(len==0)
return 0;
int l1=haystack.length();
int l2=needle.length();
int next[]=new int[l2];
getNext(needle,next);
int i=0,j=0;
while(i<l1){
if(haystack.charAt(i)==needle.charAt(j)){
i++;j++;
if(j==l2)
return i-l2;
}
else{
if(j==0)
i++;
else{
//需查找的字符串指針回退
j=next[j-1]+1;
}
}
}
return -1;
}
}