搞懂KMP,,这一片文章就够了

今日任务
●28. 实现 strStr()
●459.重复的子字符串
●字符串总结
●双指针回顾

KMP算法

认识前缀表

  1. KMP算法较于暴力解法的优化:KMP算法简化了查找过程中的遍历次数
  • 暴力解法中当遇到冲突时,是将模式串后移一位,然后再和文本串进行比较,其事件复杂度为O(M*N)。
  • KMP算法为当遇到冲突时,下一次开始位置为:冲突位之前的字符串的最长相等前后缀的前缀的下一位开始
  1. KMP算法为什么要使用前缀表:前缀表中记录了不匹配字符与之前所有字符构成的字符串的所有字串子串的最长相等前后缀的值
  2. 什么是前缀,什么是后缀:
  • 前缀为包含首字符但不包含包含尾字符的所有子串
  • 后缀为不包含首字符但包含尾字符的所有字串
    如:模式串为 a a b a a f,其所有子串的前后缀及最长相等前后缀的长度如下:
    在这里插入图片描述
    前缀表(next数组):前缀表中记录了一个字符串的所有子串的最长相等前后缀的值
    如aabaaf的前缀表为[0,1,0,1,2,0],而遇到冲突时下一次的开始位置下标为:冲突位的前一位对应前缀表的值
    如匹配aabaaf时在f位发生冲突,下一次开始匹配位的下标为 f前的字符a对应的next数组值:2,因此从开始匹配(遇到冲突时指向文本字符串位置的指针不动)

实现一个字符串的前缀表

解过程为以下四部:

  • 初始化:初始化指向前缀末尾的指针值、指向后缀末尾的指针值、前缀表next数组
  • 处理前缀末尾值与后缀末尾数组不相等的情况
  • 处理前缀末尾值与后缀末尾值相等的情况
  • 更新next数组

实现过程:

//求模式串的前缀表组成的next数组
    public static int[] nextArray(String str) {
        char[] chars = str.toCharArray();
        //初始化:
        // j:指向前缀的末尾,且代表i(包括i)之前的子串最长相等前后缀的长度;
        // i指向后缀的末尾
        int[] next = new int[chars.length];
        int j = 0;
        //第一个字符组成的前缀的最大相等前后缀的长度为0
        next[0] = 0;
        //比较子串的最长前缀和最长后缀的最后一个字符是否相等
        for (int i = 1; i < chars.length; i++) {
            //前后缀不相等的情况
            while (j > 0 && chars[i] != chars[j]) {
                //j向后回退,回退位置为冲突位的前一位对应的next数组的值
                j = next[j - 1];
            }
            //前后缀相等的情况
            if (chars[i] == chars[j]) {
                j++;
            }
            //更新next的值
            next[i] = j;
        }
        return next;
    }

KMP算法匹配子串

//KMP算法匹配子串
    public static int strStr1(String haystack, String needle) {
        //求needle子串的前缀表
        int[] next = nextArray(needle);
        //查找是否存在子串
        char[] charh = haystack.toCharArray();
        char[] charn = needle.toCharArray();
        int a = 0;
        int b = 0;
        while (a < charh.length && b < charn.length) {
            //匹配文本串与模式串的第一位字符相等的位置
            if (b == 0 && charn[b] != charh[a]) {
                a++;
            } 
            //遇见冲突,模式串的指针回退,回退位置为:冲突位的前一位对应next数组中的值;文本串的指针站立不动
            else if (b != 0 && charn[b] != charh[a]) {
                b = next[b - 1];
            } 
            //无冲突(模式串指针和文本串对应字符相等):两个指针一致前进
            else if (charn[b] == charh[a]) {
                b++;
                a++;
            }
        }
        //跳出循环时a值等于文本串的长度,b值等于模式串的长度
        if (b == charn.length) {
            return a - b;
        }
        return -1;
    }
  • 28.实现 strStr()
    暴力解决:O(M*N)
public class KMPSubString {
    public static int strStr(String haystack, String needle) {
        char[] w = haystack.toCharArray();
        char[] m = needle.toCharArray();
        int ws = w.length;
        int ms = m.length;
        for (int i = 0; i < ws - ms + 1; i++) {
            int a = i;
            int b = 0;
            while (b < ms && w[a] == m[b]) {
                a++;
                b++;
            }
            if (b == ms) {
                return i;
            }
        }
        return -1;
    }

KMP算法匹配子串

public static int strStr1(String haystack, String needle) {
        //求needle子串的前缀表
        int[] next = nextArray(needle);
        //查找是否存在子串
        char[] charh = haystack.toCharArray();
        char[] charn = needle.toCharArray();
        int a = 0;
        int b = 0;
        while (a < charh.length && b < charn.length) {
            //匹配文本串与模式串的第一位字符相等的位置
            if (b == 0 && charn[b] != charh[a]) {
                a++;
            }
            //遇见冲突,模式串的指针回退,回退位置为:冲突位的前一位对应next数组中的值;文本串的指针站立不动
            else if (b != 0 && charn[b] != charh[a]) {
                b = next[b - 1];
            }
            //无冲突(模式串指针和文本串对应字符相等):两个指针一致前进
            else if (charn[b] == charh[a]) {
                b++;
                a++;
            }
        }
        //跳出循环时a值等于文本串的长度,b值等于模式串的长度
        if (b == charn.length) {
            return a - b;
        }
        return -1;
    }

    //求模式串的前缀表组成的next数组
    public static int[] nextArray(String str) {
        char[] chars = str.toCharArray();
        //初始化 j:指向前缀的末尾,且代表i(包括i)之前的子串最长相等前后缀的长度;
        // i指向后缀的末尾
        int[] next = new int[chars.length];
        int j = 0;
        //第一个字符组成的前缀的最大相等前后缀的长度为0
        next[0] = 0;
        //先比较子串的最长前缀和最长后缀的最后一个字符是否相等
        for (int i = 1; i < chars.length; i++) {
            //前后缀不相等的情况
            while (j > 0 && chars[i] != chars[j]) {
                //j向后回退,回退位置为冲突位的前一位对应的next数组的值
                j = next[j - 1];
            }
            //前后缀相等的情况
            if (chars[i] == chars[j]) {
                j++;
            }
            //更新next的值
            next[i] = j;
        }
        return next;
    }
}

相关题目:(明日KMP)
459.重复的子字符串
在这里插入图片描述

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值