【leetcode】28. Implement strStr() 字串匹配KMP BM

knzeus發表於2019-05-10

1. 題目

Implement strStr().

Returns the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack.

2. 思路

BM演算法和KMP演算法。
各實現了一版。
KMP相對實現簡單一點,原理是對當失配時,根據已匹配的字首來計算最長可跳躍的距離。跳躍的原理是已匹配過的字首部分對應的字串的前字尾相等的最大距離。實現上就是next陣列的計算。

BM演算法複雜一點。
BM演算法是利用字尾進行匹配。每次都是從查詢串needle的後面往前面進行比較。
當不匹配的時候,進行最大距離的跳躍。跳躍距離基於三點:

  1. 當前字元沒有在needle中出現過,則可以把needle的頭移到匹配失敗的下一個位置。

  2. 已經匹配好的字尾部分,在needle中從後往前的最近一次的重複出現位置,把重複位置移到當前位置來。

  3. 如果沒有字尾匹配上,但是可能有字首能部分匹配上字尾,這是可以把字首部分匹配的部分移到字尾匹配點來。
    其實2、3是不重疊的。每次跳躍三個條件中的最大的距離。

最難的地方是如何計算出字尾匹配的跳躍位置。做法是,先對needle的每一個位置,都計算出當前位置下的最長字尾匹配距離。比如第i位是suf[i]=K, 意味著{i-k+1, .., i} == {N-K, …, N-1},且{i-k} != {N-k-1}。如果第i位是最近的字尾則N-k-1的匹配字尾就是{i-k-+1,…,i},即i移動到N-1上來,移動距離就是N-1-i. 即step[N-k-1] = N-1-i.
計算時,基於suf陣列,當suf i的值不是0,就可以得到step[N-suf[i]-1] = N-1-i. i從0開始往後遍歷就可以得到字尾匹配的跳躍距離了。由於是從前往後計算,如果前面的不是最近的字首就會被後面的覆蓋,所以不用特殊考慮是不是最近的字尾匹配點。
字首匹配的一般是字尾匹配不上才會有的。所有可以先計算字首匹配的跳躍距離,然後疊加計算字尾距離即可,即字尾可以覆蓋字首。
字首的跳躍距離基於suf陣列可以比較簡單的算出來,不詳細敘述了。

3. 程式碼

耗時:3ms

class Solution {
public:
    int strStr(string haystack, string needle) {
        return bm(haystack, needle);
        return kmp(haystack, needle);
    }
    
    int bm(string& haystack, string& needle) {
        int t_sz = needle.length();
        if (t_sz == 0) { return 0; }
        if (haystack.length() == 0 || haystack.length() < t_sz) { return -1; }
        
        int t1[128];
        int t2[t_sz];
        build_bm_bad_char_table(needle, t1);
        build_bm_bad_suffix_table(needle, t2);
        
        int len1 = haystack.length();
        int len2 = needle.length();
        int i = len2 - 1;
        while (i < len1) {
            int j = 0;
            while (j < len2) {
                if (haystack[i - j] == needle[len2 - 1 - j]) {
                    j++;
                } else {
                    if (j == 0) {
                        i += t1[haystack[i]];
                    } else {
                        i += t2[len2 - j - 1];
                    }
                    break;
                }
            }
            if ( j >= len2) {
                return i - len2 + 1;
            }
        }
        return -1;
    }
    
    void build_bm_bad_char_table(string& s, int t[128]) {
        int len = s.length();
        for (int i = 0; i < 128; i++) {
            t[i] = len;
        }
        for (int i = 0; i < len - 1; i++) {
            t[s[i]] = len - i - 1;
        }
        return ;
    }
    
    void build_bm_bad_suffix_table(string& s, int t2[]) {
        int len = s.length();
        if (len == 0) return ;
        int suf[len];
        bm_suf(s, suf);
        // none match
        for (int i = 0; i < len; i++) {
            t2[i] = len;
        }
        // match prefix
        int k = 0;
        for (int mp = len - 2; mp >= 0; mp--) {
            if (suf[mp] != mp + 1) continue;
            for (; k < len - mp - 1; k++) {
                t2[k] = len - mp - 1;
            }
        }
        // match suffix
        for (int ms = 0; ms < len - 1; ms++) {
            if (suf[ms] > 0) {
                t2[len - suf[ms] - 1] = len - ms - 1;
            }
        }
        return ;
    }
    
    void bm_suf(string& s, int suf[]) {
        int len = s.length();
        if (len == 0) return ;
        suf[len - 1] = len;
        for (int i = len - 2; i >= 0; i--) {
            int k = 0;
            while (k <= i) {
                if (s[i - k] == s[len - 1 - k]) {
                    k++;
                } else {
                    break;
                }
            }
            suf[i] = k;
        }
        return ;
    }
    
    int kmp(string& haystack, string& needle) {
        int len1 = haystack.length();
        int len2 = needle.length();
        if (len2 > len1) return -1;
        if (len2 == 0) return 0;
        int next[len2];
        kmp_next(needle, next);
        int i = 0;
        int j = 0;
        while (i < len1) {
            if (haystack[i] == needle[j]) {
                i++;
                j++;
                if (j == len2) {
                    return i - len2;
                }
            } else {
                if (j == 0) {
                    i++;
                } else {
                    j = next[j - 1] + 1;
                }
            }
        }
        return -1;
    }
    
    void kmp_next(string& s, int next[]) {
        int len = s.length();
        if (len == 0) return ;
        next[0] = -1;
        for (int i = 1; i < len; i++) {
            int j = next[i-1];
            next[i] = -1;
            while (j != -1) {
                if (s[j+1] == s[i]) {
                    next[i] = j + 1;
                    break;
                } else {
                    j = next[j];
                }
            }
            if (j == -1 && s[0] == s[i]) { next[i] = 0; }
        }
    }
};

相關文章