本文只是一個學習後的總結,可能會有錯誤,歡迎各位指出。任意轉載。算法
題目:給定一個字符串 str1 和一個字符串 str2,在字符串 str1 中找出字符串 str2 出現的第一個位置 (從0開始)。若是不存在,則返回 -1。數組
str1 = aaaaabcabc
學習
str2 = abcabcaa
優化
前段時間偶然接觸到左神的算法講解視頻,大概三天的時間,反反覆覆把 KMP 算法看了三遍。終於有了一些本身的理解與體會。用傳統的 KMP 算法去作字符串匹配,實際上是用 next 數組對暴力算法的一個優化。另一種理解是將 KMP 算法理解爲動態規劃,這裏不詳細敘述。編碼
這裏我分爲三部分來說。spa
- 暴力解法
- KMP 算法
- 如何求 next 數組
暴力解法
暴力算法看起來很是簡單,實際編碼仍是須要處理一些細節的,建議寫一寫。這裏的給 str1 一個 i 指針,給 str2 一個 j 指針。i 的第一個初始位置是 0,最後一個初始位置是 str1.length - 1。指針
-
str1[ i ] 和 str2[ j ]相等: i 和 j 都日後移動一位。code
-
str1[ i ] 和 str2[ j ]不等,j 歸 0, i 從下一個初始位置開始比較。視頻
若是 j 可以到 length 這個位置上,說明從第 0 位到第 str2.length - 1 位都已經相等了,此時返回 i - j ,就是 str2 在 str1 中出現的第一個位置的 index 。字符串
若是 i 到達了最後一個初始位置,也就是 str1.length - 1 ,此時尚未匹配成功,那麼說明永遠都沒辦法匹配到 str2 。 這個時候返回 -1 。
代碼:
public int strStr(String str1, String str2) { int length1 = str1.length(); int length2 = str2.length(); if(length2 == 0) return 0; if(length1 < length2) return -1; int i = 0; while(i < length1){ int j = 0; while(i < length1 && j < length2 && str1.charAt(i) == str2.charAt(j)){ i++; j++; } if(j == length2){ return i-j; } i = i - j + 1; } return -1; }
仍是建議動手寫一下。
KMP算法
這裏暫時先不討論 next 是如何來的。你須要知道它存放的是 str2 的一些信息。他的值等於 str2 前面的全部字符造成的字串的前綴等於後綴的最大值。這裏很是繞,舉個例子來講明:
index 等於 6 的時候, 字串是 a b c a b c
。
先後綴取 1 的時候,前綴爲 a, 後綴爲 c,此時不等。 next 不能取 1 。
先後綴取 2 的時候,前綴爲 ab, 後綴爲 bc, next 不能取 2 。
先後綴取 3 的時候,前綴爲 abc, 後綴爲 abc, 此時相等, next 能夠取 。
先後綴取 4 的時候,前綴爲 abca, 後綴爲 cabc, next 不能夠取 4 。
先後綴取 5 的時候,前綴爲 abcab, 後綴爲 bcabc, next 不能夠取 5 。
先後綴不能夠取 6 。由於先後綴不能夠爲字符串自己。
index:0 1 2 3 4 5 6 7 8 9
str1 = a a a a a b c a b c
str2 = a b c a b c a a
next:-1 0 0 0 1 2 3 1
接下來是 KMP 算法的流程。按照暴力的解法,咱們仍是有兩根指針 i 和 j。
- 兩個元素相等時: i 和 j 日後移動一位。
- 兩個元素不等時: j = next [ j ],若是此時 next[ j ] 等於 -1,說明 j 指針已經移動到了最前面。
咱們來仔細理解這個不相等的兩種狀況,這裏是難點。
next[j] != -1
,這種狀況下,j 指針直接跳到 str2[next[j]]
去。爲何這樣作能夠?舉例子。
index 0 1 2 3 4 5 6 7
str1 = a b c f a b c x
str2 = a b c f a b c y
next=-1 0 0 0 0 1 2 3
在 index 爲 6的時候,i = j = 7,這個時候兩個元素不相等,咱們會把 j 跳到 str2[next[j]]
,也就是 j = 3
。這個時候 str1 前面的子串 和 str2 前面的子串是相等的,他們擁有共同的 next 數組。j 跳到 3,這個 3 表明: y/x 前面這個子串他的前三位和後三位相等。那麼,咱們的 y 的子串前三位 和 x 子串的後三位這個時候是否是就不須要比較了,由於這個 3 默認了他們相等。那麼前三位(index爲 0 1 2)就不須要比較了,直接比較第四(index 爲 3 )位。這裏就是 next 數組的核心。在左神的視頻裏面講得更直觀。
str1 = a b c f a b c x
str2 = * * * * a b c f a b c y
比較 x 與 f 是否相等。
next[j] == -1
,這種狀況下,j 已經來到最前面了,沒辦法繼續前移,那麼只能 i 向後移。
代碼:
public static int getIndexOf(char str1[], char str2[]) { if(str1.length == 0 || str1.length < str2.length) { return -1; } if(str2.length == 0) { return 0; } int i = 0; int j = 0; int next[] = getNextArray(str2); //對應三種狀況 while( i < str1.length && j < str2.length) { if(str1[i] == str2[j]) { i++; //兩個元素相等 j++; }else if(next[j] == -1) { i++; //next[j] == -1 }else { j = next[j];//next[j] != -1 } } return (j == str2.length) ? i-j : -1; }
next數組
str2 = a b c f a b c y
next=-1 0 * * * * * *
第一位默認爲 -1。 由於第一位元素沒有子串。
第二位默認爲 0。由於第二位元素的子串只有一個元素,那他的先後綴最大相等數目只能爲0。
接下來是第三位,第三位的子串是a b
,這裏是難點。如何求出它的 next 值。j = 3
用 j - 1 的 next 的值,cn = next[j-1]
的 str2 對應的元素, 和 str2[j-1]
比較。這裏的cn = 0, 那比較的就是第 0 號元素和第 1 號元素的值。比較出來必定有兩種狀況,相等,不相等。而在不相等的時候又要分兩種狀況。
index 0 1 2 3 4 5 6 7
str2 = a b c f a b c y
next=-1 0 0 0 0 1 * *
爲了更直觀看見,我換個例子。j = 6.
cn = next[j-1] = 1, str2[cn] = b
str2[j-1] = b
這個時候是相等的,所以 next[6] = ++cn = 2
。爲何?
這個 cn 表明的是什麼?cn 表明的是 j-1
位的 next 值,這個值表明 j-1
位的先後綴最大值。這個最大值是 1,說明他第一位和最後一位相等。那麼比較他的第二位(str2[cn]
)和最後一位的下一位(str2[j-1]
)是否相等。相等的話,next[6] = ++cn = 2
。不相等怎麼辦?分爲兩種狀況。
cn > 0,cn = next[cn]
cn<= 0,next[j] = 0
這裏又是爲何,就是在子串的狀況下繼續分,去找到和str[j-1]
相等的 cn
,若是一直找不到呢?怎麼辦,那next[j] = 0
。
代碼:
public static int[] getNextArray(char []str) { if(str.length == 1) { return new int [] {-1}; } int next[] = new int [str.length]; next[0] = -1; next[1] = 0; int i = 2; int cn = 0; while( i < str.length) { if(str[i-1] == str[cn]) { next[i++] = ++cn; }else if(cn > 0) { cn = next[cn]; }else { next[i++] = 0; } } return next; }
小結一下
- 暴力解法,多去寫,多寫兩遍就熟練。
- KMP 具體實現,有三種狀況。元素相等、元素不等且 next 不等於-一、元素不等且 next 等於 -1。
- next 的求解方法,也是三種狀況。cn 和 j-1 對應的元素相等、 對應的元素不等且cn>0、 對應的元素不等且cn<=0。
公衆號 stul 同步更新算法學習過程,歡迎關注。