day9 | KMP

91 阅读2分钟

28.找出字符串中第一个匹配项的下标

题目链接:leetcode.cn/problems/fi…

要点

  • 什么是前缀表? 记录下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。
  • 前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串;后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串
  • i指向后缀的末尾,j指向前缀中相同前后缀子串的下一个元素(即,当比较发现不同时,跳向的下一个比较位置)
  • j要增加只能+1

前缀表即next数组

class Solution {
public:
    void getnext(int* next, const string& s){
        int j = 0;
        next[0] = 0;
        for (int i = 1; i < s.size(); i++) {
            // 不匹配!跳回
            while (j>0 && s[i] != s[j]) {
                j = next[j-1];
            }
            // 匹配就+1
            if (s[i] == s[j]) j++;
            next[i] = j;
        }
    }

    int strStr(string haystack, string needle) {
        int next[needle.size()];
        getnext(next, needle);
        int j = 0;

        // i 只能向前走,有且只有两种情况
        //      1. j != 0, ij指向的字符匹配时,一起后移一位
        //      2. j == 0, 第一位就不匹配,i后移
        // 其他情况都是j回退,找字符去匹配i的指向
        for (int i = 0; i < haystack.size(); i++) {
            while (j>0 && haystack[i] != needle[j]) {
                j = next[j-1];
            }
            // 执行到这里的情况:
            // 1. j == 0 -> haystack[i] == needle[0]  2. haystack[i] == needle[j]
            //           \->haystack[i] != needle[0]
            if (haystack[i] == needle[j]) j++;    // j先+1,i在循环体末尾+1

            // j将needle都匹配完了,匹配成功,返回
            if (j == needle.size()) return i-needle.size()+1;
        }
        return -1;
    }
};

难点

  • getnext函数和主函数结构类似,主要有如下三步:
  1. 初始化
  2. 处理前后缀不相同的情况 (while)
  3. 处理前后缀相同的情况 (if)

总结

n为文本串长度,m为模式串长度,因为在匹配的过程中,根据前缀表不断调整匹配的位置,可以看出匹配的过程是O(n),之前还要单独生成next数组,时间复杂度是O(m),所以整个KMP算法的时间复杂度是O(n+m)的。

暴力的解法显而易见是O(n × m)