原始前缀表实现KMP字符串匹配算法

854 阅读3分钟

一、BF算法

使用子串与主串匹配,匹配失败就退回这一趟最开始匹配的主串位的下一位继续匹配,需要的时间复杂度是O(m * n),m为子串长度,n为主串长度。

二、KMP

(1)什么是前缀表

使用前缀表,记录每个字符的最长相等前后缀的长度。

特性:在遇到不匹配的位置的时候,找前一位的子串它的最长相等前后缀的长度,将子串下标回退到长度对应的位置。(选择前缀表的原因)

前缀表有什么作用:用来回溯的,它记录了模式串和主串不匹配的时候,模式串应该从哪里开始重新匹配。前缀表里的数值代表这就是:当前位置之前的子串有多大长度相等的前缀后缀。

什么是前缀:包含首字母,不包含尾字母的子串

比如aabaaf中,f的前缀是a,aa,aab,aaba,aabaa

什么是后缀:包含尾字符,不包含首字符的子串

比如aabaaf中,f 的后缀是f,af,aaf,baaf,abaaf。

原始的前缀表:

a a b a a f

0 1 0 1 2 0

(2)求next数组

1.初始化

2.处理前后缀不相同的情况

3.处理前后缀相同的情况

4.赋值next数组

使用两个指针:

  • i 表示后缀末尾位置;
  • j 表示前缀末尾位置,同时也是最长相等前后缀的长度。

(3)前缀表的变形

常见的前缀表变形为原始前缀表元素统一减1,或者右移一位,第一位以-1填充。

不管是哪种方法,甚至是直接使用元素的前缀表,都是可以使用KMP算法,关键在于如何去使用next,如果是使用原始前缀表的话,在遇到匹配不成功的情况,将子串的下标移动到当前字符的前一位字符的next[ j - 1 ]即可,如果 j=0,则主串下标加1即可

(4)求原始next数组

// 求原始前缀表
public void getNext(String pattern, int[] next) {
    if(pattern == null || pattern.length() == 0)    {
        return ;
    }

    int i;              // 后缀末尾位置
    int j = 0;          // 前缀末尾位置,同时也是最长相等前后缀长度
    next[0] = 0;

    // 遍历子串长度
    for(i = 1; i < pattern.length(); i++) {
        while(j > 0 && pattern.charAt(i) != pattern.charAt(j)) {
            j = next[j - 1];                    // 如果不匹配,则子串下标移动到前一位的最长前后缀相等长度
        }

        if(pattern.charAt(i) == pattern.charAt(j)) {
            j++;                    // 匹配的子串长度加一
            next[i] = j;            // 更新next数组的值
        }
    }
}

(5)使用原始前缀表进行主子串字符匹配

// 使用原始前缀表进行主子串字符匹配
public int indexOf(String target, String pattern) {
    // 题目要求子串为空字符串时,返回0
    if(pattern.length() == 0) {
        return 0;
    }

    int[] next = new int[pattern.length()];

    // 如果子串长度大于主串长度,则返回-1,表示主串不包含子串
    if(pattern.length() > target.length()) {
        return -1;
    }

    getNext(pattern, next);             // 获取前缀表next数组

    int n = target.length();            // 主串长度
    int m = pattern.length();           // 子串长度
    int i = 0;                          // 遍历主串的下标
    int j = 0;                          // 遍历子串的下标

    // 只要主串或子串遍历完成,则退出循环
    while(i < n && j < m) {
        // 如果主串和子串字符匹配成功,下标同时加一
        if(target.charAt(i) == pattern.charAt(j)) {
            i++;
            j++;
        } else {
            // 如果匹配不成功,且子串下标为0,则主串下标加一,否则子串下标移动到前一个字符的最大相等前后缀长度
            if(j == 0) {
                i++;
            } else {
                j = next[j - 1];    
            }
        }
    }

    return j == m ? i - m : -1;            // 如果匹配成功,返回起始子串下标
}

三、实现 strStr()

力扣练习题: leetcode-cn.com/problems/im…

image.png

image.png