1.前言
在一個字符串中尋找是否包含目標字符串,實現這個要求並不難,遍歷文本的每個字符串,如果和目標字符串的第一個匹配,就把匹配的字符后移一位繼續對比,直到不匹配,然后將文本的指針后移一位,繼續對比即可。但是這樣的暴力匹配最壞情況的時間復雜度為O(n*m),而KMP算法可以將其復雜度降低到O(n+m),減少重復對比次數。
2.正文
在學習KMP算法時,我翻閱了不少博客,但是五花八門的KMP介紹讓我有點迷糊,有時候似乎看懂了,但是換一個例子我似乎不明白應該如何構造next數組,直到看了這個視頻,對於KMP不懂的小伙伴們可以看看這個視頻,視頻大概在4-12分鍾中講解了如何構造利用next數組,並如何使用next數組減少重復對比。
比起那些干澀的博客,這個視頻提供了很好的例子說明如何在O(n)級數下構造next數組(利用已經求得的前面部分next來構造后面的部分),並且提供了幾個例子生動的說明,next數組的作用:記錄當前的后綴字串與前綴子串最大匹配長度。
在這里不具體說明,有問題的小伙伴們可以去看看上面的視頻,看完之后嘗試自己動手寫一個kmp算法。下面我會貼出看完這個視頻后我自己實現的kmp。
3.實現
以下是KMP算法實現,主要包含kmp函數返回-1(未匹配)或匹配的第一個位置(下標從0開始),本人的next數組下標是從0開始到length-1,還包括一個getNext函數用來構造next數組,沒有考慮text以及pattarn的一些異常輸入,主要是為了更單純的實現kmp算法。
1 static int kmp(char[] text,char[] pattarn){ 2 int[] next = new int[pattarn.length]; 3 getnext(pattarn,next); 4 int m = 0;//matchLength 5 for(int i = 0;i<text.length;i++){ 6 while(m>0&& pattarn[m] != text[i]){ 7 m = next[m-1]; 8 } 9 if(text[i] == pattarn[m]){ 10 m++; 11 if(m == pattarn.length){ 12 return i - m+1; 13 } 14 } 15 } 16 return -1; 17 } 18 19 private static void getnext(char[] pattarn, int[] next) { 20 int q = 0;//q代表前一個字符前后綴能匹配的最大長度 21 for(int i = 1;i<pattarn.length;i++){//next[0] = 0,因此從1開始 22 while(q > 0 && pattarn[q] != pattarn[i]){//遞歸直到q為0(沒有匹配的前綴)或者當前字符與q相等時(不斷“遞歸”查前綴匹配的前一個位置q) 23 q = next[q-1];//如果不相等,如“acad”,i=3,q=1,則q變成next[q-1](q-1是不匹配的前一個位置) 24 } 25 if(pattarn[q] == pattarn[i]){ 26 q++; 27 } 28 next[i] = q; 29 } 30 }
對於更完善的kmp以及入參的異常處理實現在下面貼出,也已經通過leetcode 28 Implement strStr()。其實就是對傳入參數進行了一些意外值處理。
1 class Solution { 2 public int strStr(String haystack, String needle) { 3 if (needle.length() == 0) 4 return 0; 5 if (haystack.length() < needle.length()) 6 return -1; 7 8 char[] text = haystack.toCharArray(); 9 char[] pattarn = needle.toCharArray(); 10 int[] next = new int[pattarn.length]; 11 getnext(pattarn,next); 12 int m = 0;//matchLength 13 for(int i = 0;i<text.length;i++){ 14 while(m>0&& pattarn[m] != text[i]){ 15 m = next[m-1]; 16 } 17 if(text[i] == pattarn[m]){ 18 m++; 19 if(m == pattarn.length){ 20 return i - m+1; 21 } 22 } 23 } 24 return -1; 25 } 26 27 private static void getnext(char[] pattarn, int[] next) { 28 int q = 0;//q代表前一個字符前后綴能匹配的最大長度 29 for(int i = 1;i<pattarn.length;i++){//next[0] = 0,因此從1開始 30 while(q > 0 && pattarn[q] != pattarn[i]){//遞歸直到q為0(沒有匹配的前綴)或者當前字符與q相等時(不斷“遞歸”查前綴匹配的前一個位置q) 31 q = next[q-1];//如果不相等,如“acad”,i=3,q=1,則q變成next[q-1](q-1是不匹配的前一個位置) 32 } 33 if(pattarn[q] == pattarn[i]){ 34 q++; 35 } 36 next[i] = q; 37 } 38 } 39 }
在理解了KMP的流程后,趕緊動手實現一下吧。實現完還可以去leetcode測試一下哦!