代码随想录算法训练营第九天| 28. 实现 strStr()

题目链接:28. 实现 strStr()

KMP算法的经典应用

  • 首先讲暴力求解
    两层for循环,外层控制文本串,内层控制模式串,不停进行匹配过程中,如果有一个没有匹配上,那么外层回退到这一轮匹配开始前的后一个(以上一个字符开始匹配不上,就从下一个字符开始匹配),而内层模式串要从头开始匹配,所以指针回退到开始,并重新进行匹配。时间复杂度在 O ( m ∗ n ) O(m * n) O(mn),其中n为文本串长度,m为模式串长度。

  • 什么是前缀和后缀

    • 前缀是指所有以第一个字符开头的且不包含最后一个字符的连续子串
    • 后缀是指所有以最后一个字符结尾的且不包含第一个字符的连续子串

注意:后缀仍然是原字符串的子串,要从前往后看,而不是从后往前。
例如字符串aabaaf中,前缀包括a, aa, aab, aaba, aabaa,后缀包括f,af, aaf, baaf, abaaf

  • 什么是前缀表及前缀表的计算

前缀表要求的是最长相等前后缀。
字符串a的最长相等前后缀为0(因为只有一个字符,前缀和后缀都为0)

字符串最长相等前后缀
a0
aa1
aab0
aaba1
aabaa2
aabaaf0

那么就得到了字符串aabaaf的前缀表010120

  • next数组及如何用next数组进行字符串的匹配

这里可以简单的把前缀表当成next数组,即next = [0, 1, 0, 1, 2, 0]
文本串:aabaabaafa
模式串:aabaaf

可以观察到,当文本串匹配到第二个b字符时,和模式串的f字符不匹配,那么找到f前一个位置对应的next值,根据该值对应的数字回退到相应的模式串下标。这里f前一个字符对应的next数值为2,所以模式串英回退到字符b,并继续匹配。如果能匹配上就进行下去,反之就要继续回退。

原理:

当前字符(f)与文本串字符(b)不匹配,说明本字符前的一个(或几个)字符(b字符后的aa)与文本串是能匹配上的,而由于计算了前缀表,这就意味着模式串的前一个(或几个)字符(b字符前的aa)是能够和文本串匹(第二对)配上的,那么只需要让模式串回溯到aa后的字符b,并继续与文本串的第二个b进行匹配。

主要存在以下疑惑:为什么只需要模式串回溯,而文本串不用回溯呢(就像暴力求解的过程中),会不会漏解呢?

分析:

如本例中aabaaf匹配到f处与文本串中aabaab..b字符不相同,但是这同时说明了一个问题(理解关键),即文本串不匹配的那个字符前的一系列字符aabaa是完全与模式串匹配的(也就是相同的),那么这个回溯的过程其实就可以看成模式串求next的一个等价过程了。如果像暴力法一样,下面一步是将模式串aabaaf与文本串abaa..进行匹配,且前面四位能匹配得上(假设,实际上不行),那就说明f前的a对应下标next值为4(最长相等前后缀为4),而事实上next为2(最长相等前后缀为2),所以将主串后移一位一定匹配不上。这样就能够理解为什么回溯到下标2对应的字符b了。

即我们在求解next时就记录了最长前缀与后缀的信息。而当文本串中某一位与模式串不匹配时,文本串中的前几个字符与模式串完全一致,这部分的文本串当成模式串,模式串回溯的过程就是就是找最长前缀与最长后缀的过程。

class Solution {
    public int strStr(String haystack, String needle) {
        if (needle.length() == 0) return 0;
        int[] next = new int[needle.length()];
        getNext(next, needle);

        int j = 0;
        for (int i = 0; i < haystack.length(); i++) {
            while (j > 0 && haystack.charAt(i) != needle.charAt(j)) j = next[j - 1];
            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) {
        //i 指向后缀末尾, j指向前缀末尾,同时j代表着i之前(包括i)的子串的最长相等前后缀长度。
        int j = 0;
        next[0] = 0;
        for (int i = 1; i < next.length; i++) {
            while (j > 0 && s.charAt(i) != s.charAt(j)) {
                j = next[j - 1];
            }
            if (s.charAt(i) == s.charAt(j)) j++;
            next[i] = j;
        }
    }
}

参考链接:
代码随想录_KMP_视频讲解

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值