給定一個字符串\(S\),求\(M\)字符串是不是\(S\)字符串中的子串.若是是,返回\(M\)對應\(S\)的第一個下標,不然返回-1.java
例如:S串爲a b c d a b c d a b c d e
算法
M串爲a b c d e
數組
結果:返回S串下標8.測試
以前看過這種求子串的題,可是隻在腦海中想象了一下,沒有動手寫出算法.spa
看到的時候內心就嘀咕確定不能暴力求解,須要讓子串\(M\)進行跳躍,可是沒有具體地研究邊界問題,也沒有動手寫一個字符串驗證想法,致使想法很不靠譜.3d
以前的想法很粗糙:
code
挨個比較字符串,若是不相同,總體都跳躍,跳躍的方式是\(M\)串首位對其不匹配\(S\)串的下標位置.而後繼續比較
blog
大約就是這個樣子,想到這就沒繼續往下想也沒有去實現,這顯然是錯誤的.跳躍是應該跳躍,可是不是那麼粗糙的跳躍,假如字符串和子串是下面這種狀況這種狀況:
字符串
以我那種錯誤的想法是會產生bug的:
get
結果是\(S\)串不會包含\(M\)串,但事實上是包含的.(下文將目標串統一稱做\(S\),子串統一稱做\(M\))
那咱們就應該考慮如何正確的跳躍.
知道了以前的想法是錯誤的,咱們就應該找到符合各類邊界的跳躍方式.只要肯定了跳躍方式,KMP就很容易寫出來了.KMP中提到了前綴表(或者最大相同先後綴)這些概念.前綴表暫時先不考慮,只談談如何進行跳躍才能知足邊界.
繼續回到以前的圖,若是咱們想要成功地找到下標,正確的跳躍方式應該是下圖這樣:
注意\(M\)串錯誤匹配項(\(M[5]\)元素d
)以前的元素(藍色填充的a,b
).
好,爲何這麼跳呢?
當肯定\(S[5]\)和\(M[5]\)不匹配的時候,咱們能夠肯定d
以前的項都是匹配的.即\(S[0-4]\)和\(M[0-4]\)是匹配的.
\(M[0-4]\)
對於已經匹配的\(M[0-4]\),前綴幾項與後綴幾項相同(即\(M[0]\),\(M[1]\)和\(M[3]\),\(M[4]\)是相同的),那麼把\(M\)平移使\(M[0]\)對齊\(M[3]\)以前所處的位置,這必定能確保\(M[0]\),\(M[1]\)和以前\(M[3]\),\(M[4]\)與\(S\)對應的\(S[3]\),\(S[4]\)是匹配的,即必定能確保\(M[0]\),\(M[1]\)和\(S[3]\),\(S[4]\)是匹配的.
平移
那咱們如何利用這個特色跳躍呢,如今咱們生成一個輔助的先後綴相同的數組.
以a,b,c,a,b,d
爲例,
M產生的子串 | 相同先後綴個數 | 備註:就把相同先後綴數組稱爲\(D\) |
---|---|---|
a | 0 | |
a,b | 0 | |
a,b,c | 0 | |
a,b,c,a | 1 | |
a,b,c,a,b | 2 | |
a,b,c,a,b,d | 0 | 最後一個匹配就能夠返回了,因此用處不大 |
最後生成一個相同先後綴的數組能夠與\(M\)對應起來
如今只須要解釋一下如何使用相同先後綴數組進行跳躍,應該就能寫出KMP的代碼了.
當\(M[5]\)與\(S[5]\)不相同時,看一下先後綴數組\(D\)前一個位置的值,即\(D[4]\)的值爲2,那麼只須要將\(M[2]\)平移至\(M[5]\)的位置上就能夠了.若是\(M[3]\)不匹配,後綴數組\(D\)的前一位\(D[2]\)是0,那麼就把\(M[0]\)平移對其\(M[3]\)的位置就能夠了.
假如\(M[0]\)不匹配的話,就直接日後跳一位,與\(S\)下一項比較.
總體思路就大體如此了,若是理解了應該能夠按照思路寫出代碼了.
下面就貼一下代碼實現,我只是簡單地測試了一下,沒有大量測試,可是總體思路應該大體如此,可能有些邊界問題尚未考慮周全,歡迎指正.
public static int getIndexOf(String s, String m) { if (s == null || m == null || m.length() < 1 || s.length() < m.length()) return -1; char[] ss = s.toCharArray(), ms = m.toCharArray(); int si = 0, mi = 0; int[] next = getMatchArray(ms); int q = next[mi]; while (si < ss.length && mi < ms.length) { if (ss[si] == ms[mi]) { si++; mi++; } else { if (mi != 0) { mi = next[mi - 1]; } else { si++; } } } return mi == ms.length ? si - mi : -1; } public static int[] getMatchArray(char[] ms) { if (ms.length == 1) return new int[]{0}; int[] next = new int[ms.length]; next[0] = 0; int j = 0, tail = 1; while (tail < ms.length) { j = next[tail] = ms[j] == ms[tail] ? (j + 1) : 0; tail++; } return next; } public static void main(String[] args) { String str = "abcabcababaccc"; String match = "ababa"; System.out.println(getIndexOf(str,match)); }