字符串——6.力扣题目:28. 找出字符串中第一个匹配项的下标

题目链接

解析:(KMP算法)

题目要求:给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1 。

示例 1:

输入:haystack = "sadbutsad", needle = "sad"
输出:0
解释:"sad" 在下标 0 和 6 处匹配。
第一个匹配项的下标是 0 ,所以返回 0 。

大体思路:这道题是典型的KMP算法题。需要寻找最长公共前后缀来做。

KMP算法

作用:解决字符串匹配问题。

核心思路:当遇到不匹配的情况时,寻找不匹配位置的前一位的子串的最长相等前后缀,找到后,从相等的前缀位置后一位继续匹配。

原理:相比于暴力法,KMP关注已经匹配过的字符串,若出现了不匹配,如何充分利用已经匹配的部分。

为什么要找相等的前缀和后缀?因为可以把匹配过的后缀当成即将重新匹配的前缀,这样就不需要再匹配与后缀相等的前缀了,节省了开销。

为什么不像暴力法一样,如果当前匹配失败,从文本串开头的后一位开始匹配,而是从文本串中不匹配的字符开始?因为子串一旦满足前缀和后缀相等,子串相等的前后缀可以看做是关于中心对称的,能匹配的只能是两端对称的部分,其他部分不能匹配。这样做,很大地节省了开销。

&[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yKXyDBMY-1679492028860)(C:\Users\张凯旗\Pictures\Camera Roll\KMP精讲1.gif)]

制作next数组:

实现方法

​ (1)制作next数组:先对模式串 needle 的所有子串找出最长相等子序列,按照next下标位置放入next数组中。

​ ①初始化:

​ 定义一个next数组,用于记录模式串的所有子串的最长相等前后缀长度,next的下标即为子串在模式串中的位置。

​ 定义i记录最长相等前后缀的后缀末尾,j记录最长相等前后缀的前缀末尾(j同时也记录最长相等前后缀的长度)。

​ i初始值为1,j初始值为0.(为什么i初始值为1,j和i不指向同一位置,这样才可以相互比较,i又是指代后缀,所以i在j后面。如果i初始化为0就是死循环)

​ ②从0到末尾for循环i。每次循环考虑两种情况:

​ 情况一、j对应的值和i对应的值不相等,j向前回退,一直回退(所以是while循环)到和i对应的值相等,或者一直回退到0位置。

​ 情况二、j对应的值和i对应的值相等,j++。

​ 经过①和②就得到了next表

​ (2) 寻找符合要求的下标:

​ 循环遍历haystack字符串
​ 如果和needle模式串不匹配,while回退j到next[j-1],也就是将当前后缀回退前一位的在next数组中对应的位置。一直到匹配或者j到开头的数组。

​ 如果和needle模式串匹配,i和j都一起向后迭代。指导j == 模式串的length(),说明找到了这个值。返回i - 模式串的length() - 1;

注意点:(if和while上下位置不能颠倒)

​ ① next数组初始化时,表示后缀的i需要初始化为1,不然陷入死循环。

​ ② 在(2)寻找符合要求的下标中,while循环结束了,还要执行一下if,因为while负责使得j向前回退,如果不是因为j==0结束了循环,还要把j对应的值和i对应的值比较一下,也就是执行下面的if。

遗留问题:(制作next数组中,为什么回退不可以是j–? 为什么一定要是j = next[j - 1];)
代码:
class Solution {
    public int strStr(String haystack, String needle) {
        if (haystack.length() < needle.length()) return -1;
        int[] next = new int[needle.length()];
        getNext(next, needle);
        // j表示模式串needle的下标
        int j = 0;
        // i表示haystack发下标
        for (int i = 0; i < haystack.length(); i++) {
            // 如果不相等,跳转到j前一位next对应的位置,也就是跳转到j前一位的最长相等前缀的最后一位
            while (j > 0 && haystack.charAt(i) != needle.charAt(j)) {
                j = next[j - 1];
            }
            // 当两个字符相等,或者不相等但是经过上面的while跳转到j新的位置使得两个字符相等
            // 这时候j和i一起向后迭代
            if (haystack.charAt(i) == needle.charAt(j)) {
                j++;
                if (j == needle.length()) return i - needle.length() + 1;
            }
        }
        return -1;
    }
    public void getNext(int[] next, String s) {
        // 初始化前缀为0
        int j = 0;
        // 后缀i从1开始遍历
        for (int i = 1; i < s.length(); i++) {
            // 当后缀和前缀对应的字符不同时,前缀向前回退
            //(注意:回退到j前一个位置对应的next数组的值对应的位置)
            // 为什么不可以是j--?
            while (j > 0 && s.charAt(i) != s.charAt(j)) j = next[j - 1];
            // 如果后缀和前缀对应的字符相同,则前后缀一起向后迭代
            //(注意:上一步的while执行完了会进行这个if判断,也就是如果回退到和i对应字符相等,j还要++)
            if (s.charAt(i) == s.charAt(j)) j++;
            next[i] = j;
        }
    }
}

注释:图片来自于代码随想录

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值