1. 程式人生 > 實用技巧 >leetcode之28實現strStrGolang(KMP演算法)

leetcode之28實現strStrGolang(KMP演算法)

KMP演算法

舉例

假設我們有字串:

GTGTGAGCTGG

並且有模式串

GTGTGCF

演算法解析

  • 我們需要建立模式串的next,他表示當兩個字串進行模式匹配失敗的時候,需要從模式串的哪一個位置重新開始匹配

    • 例如上面兩個字串

      012345678910
      G T G T G A G C T G G
      G T G T G C F

      我們可以看到在下標為5的地方匹配失敗了,因為字串中的字元是A,而模式串中的字元是C

      但是我們不需要再從模式串的開始的地方重新匹配,我們只需要從如下的地方重新開始

      012345678910
      G T G T G A G C T G G
      G T G T G C F

      此時在原始字串中的下標位置沒有變,但是模式串的匹配開始的地方不再是0了,而是從第4個字元開始匹配的,也就是用模式串中處於座標3T與原始字串中處於下標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()
}