leetcode之28實現strStrGolang(KMP演算法)
舉例
假設我們有字串:
GTGTGAGCTGG
並且有模式串
GTGTGCF
演算法解析
-
我們需要建立模式串的
next
,他表示當兩個字串進行模式匹配失敗的時候,需要從模式串的哪一個位置重新開始匹配-
例如上面兩個字串
0 1 2 3 4 5 6 7 8 9 10 G T G T G A G C T G G G T G T G C F 我們可以看到在下標為5的地方匹配失敗了,因為字串中的字元是
A
,而模式串中的字元是C
但是我們不需要再從模式串的開始的地方重新匹配,我們只需要從如下的地方重新開始
0 1 2 3 4 5 6 7 8 9 10 G T G T G A G C T G G G T G T G C F 此時在原始字串中的下標位置沒有變,但是模式串的匹配開始的地方不再是
0
了,而是從第4
個字元開始匹配的,也就是用模式串中處於座標3
的T
與原始字串中處於下標5
的進行比較那麼我們是怎麼得到這個下標
3
的呢?沒錯就是通過next
資料 -
總的來說,
next
陣列儲存的是這樣的資料:例如模式串
GTGTGCF
,那麼next[5]
儲存的就是在模式串下標5
前面的字串中,位於開始位置的字串和末尾位於5
之前的字串相同時,這個字串的長度就是next[5]
的值如同上圖,同一個子串,他們在模式串的兩端,那麼,這個子串的長度就是
next[]
的值了,當然,這兩個子串不能完全重疊,不然這一切就失去了意義
-
-
我們利用這
next
陣列,來減少字元比較的次數,當比較字元失敗時,就根據next換到新的位置,這個位置就是next
的值 -
或許這樣你可以想通,因為如上圖所示,位於模式串首位兩端的子串是一樣的,而整個模式串都是比較成功的,所以當我們比較模式串的下一個字元,完全可以把前面的子串挪到最後來,那麼是不是就說明了前面這部分子串的內容是不需要再次比較的了
虛擬碼
-
構建next陣列,一直模式串
sModel
-
設定
next[0]=-1
-
從
index=0,k=-1
,開始迴圈到len(sModel)-2
-
如果
k==-1 || sModel(index)===sModel(k)
-
next[++index]==++k
-
-
k=next[k]
其實這一步是好理解的,當我們已經知道了
next[i]
的值為k
,意思就是說在i之前的模式串中(最後一個字元下標是i-1
),開始的k個字元(最後一個字元下標是k-1
)和結束的k個字元是一樣的-
那麼如果
sModel[i]==sModel[k]
,是不是就是說在i+1
之前的模式串中,開始的k+1
個字元和結束的k+1
個字元是一樣,那麼也就是說next[i+1]
在這種情況下就等於next[i]+1
-
如果
sModel[i]!=sModel[k]
,那麼我們就不能像上面那樣加1
了,我們此時將k設定為next[k]
,這一步這樣理解,因為對於前k
個字元的模式創中,位於開始的next[k]
個字元和位於結束的next[k]
個字元是一樣的,並且在前i個模式串字元中,前k個字元和結束的k
個字元是一樣的,那麼我們就一定有前i個字元的模式串中,前next[k]
個字元和位於結束的next[k]
個字元是一樣的,所以我們只需要比較第next[k]+1
個字元(下標為next[k]
)和第i+1
個字元(下標為i
)是否相同即可。而這個處理又回到了我們的上一步迴圈。
-
-
如果
k=next[k]
時的結果是k=-1
,那麼我們就直接將next[index]
的值弄為0
就可以了,這表示我們需要從模式的第一個元素重新開始匹配,而next[0]=-1
表示如果第一個字元就不匹配你,我們就要從第-1
個字元開始匹配,然而沒有-1
這個元素,所以我們需要移動到主串的下一個字元
-
-
進行模式匹配
-
有了
next
陣列以後,我們就進行匹配,-
當匹配成功以後就檢查是否到了模式串的末尾,
-
如果沒到就繼續匹配
-
如果到了就輸出
-
-
當匹配到模式串的下標
index
失敗以後,就從從模式串的next[index]
的位置重新開始匹配-
如果
next[index]>=0
,那麼不移動主串的下標,直接開始匹配 -
如果
next[idnex]<0
,那麼就將主串的下標後移以為,從模式串的開頭重新開始匹配
-
-
-
實現strStr
很容易,就是在模式匹配成功的時候,用主串的下標減去模式串的下標(模式串此時的下標就是模式串的長度減1)
注意:考慮邊界條件,比如主串為空或者模式串為空,甚至兩個都為空
程式碼:
// next陣列 next := make([]int, len(needle)) // 構建next陣列的函式 createNext := func() { // 當模式串長度為0,就直接返回 if len(needle) == 0 { return } // 預設next[0]=-1 k, index := -1, 0 next[0] = -1 // 使用模式串倒數第二個字元計算最後一個字元的next值,所以這裡是<len(needle)-1 for index < len(needle)-1 { // 當字元匹配,直接加1 if k == -1 || needle[index] == needle[k] { index++ k++ next[index] = k } else { // 不匹配就更新k的值 k = next[k] } } } // 模式匹配函式 kmp := func() int { // 先處理特殊情況的空字串 if len(haystack) == 0 && len(needle) != 0 { return -1 } if len(needle) == 0 { return 0 } // 當連個字串都不為空 for h, n := 0, 0; ; { if haystack[h] == needle[n] { // 如果匹配,兩個字串的下標都加1 n++ h++ // 是否到了模式串的最後 if n == len(needle) { return h - n } // 是否到了被匹配字串你的最後 if h == len(haystack) { return -1 } } else { // 遇到不匹配的字元 n = next[n] // 從第一個字元就不匹配,那麼就要將連個字串都向後移動一個位置,因為只有next[0]=-1 // 其他最小的是next的值是0,表示需要對模式串重新從最開始匹配 if n < 0 { n++ h++ if h == len(haystack) { return -1 } } } } } createNext() return kmp() }