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

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; }
        }
    }
};

你可能感兴趣的:(leetcode,算法,c++,面试,kmp)