使用KMP算法实现strstr()函数

strstr()函数是一个匹配函数,判断一个字符串是否包含另外一个字符串,并返回被包含字符串的起始位置。如果使用暴力匹配的话,时间复杂度非常高,最佳解法是使用KMP算法,但是LeetCode把这道题目定义为简单题也是挺离谱的。
题目链接:https://leetcode.cn/problems/implement-strstr/

1、KMP介绍和原理

KMP前缀表怎么计算,为什么要使用前缀表,前缀表怎么使用代码实现,可以看一下这个链接。KMP介绍。

2、KMP理解

在已了解前缀表的基础上,加上一点自己的理解。虽然知道前缀表每一位就是对应位置上的子串的最长相等前后缀长度,但是这个长度是怎么匹配的,同时和暴力匹配之间,为什么时间复杂度会由O(MxN)变为O(M+N)呢。
我这里就举例一下,str = aabaabaafa,下方的数字表示索引,上方的数字表示前缀表next[i]的数值。

1、前缀表构建

aabaabaaf 的前缀表为 0101234501,第8位的最长相等前后缀为aabaa,所以长度为5。

在这里插入图片描述

2、构建流程

那么最后一位的我们通过列前后缀表知道是1,但是他的原理是什么呢?我们前缀表本位的值应该是由前一位推得的,由本例得到,第六位的前缀表值为4,那么代表着字符串的前4个字符是能够在字符串内找到相同的子串。其中索引 0-3 为aaba,索引3-6位aaba。

在这里插入图片描述
在这里插入图片描述

那么后一位的前缀值应该是多少呢?其实可以看到只要str[4] == str[7]的时候,那么我们的前缀表值next[7] = next[6]+1。也就是代表着str字符串的[0:4] 和 [3:7]是相同的。

在这里插入图片描述

在这里插入图片描述
但是,当我们以此类推到str[8]的时候,next[8]应该为多大呢?我们知道,前一位的值表示str的前五个字符是能够找到重复的,由于我们需要找str[0:8]的最长相等前后缀,那么首先查看str[5] ?= str[8] 。
在这里插入图片描述
在这里插入图片描述

不相同。我们可以确定,这一位的最长相等前后缀的长度一定是小于5的,同时next[8]<=next[4]+1,我们有知道str[4]的最长相等前后缀长度next[4]=2,如果str[2] == str[8]那么,next[8] = next[1]+1。
在这里插入图片描述
在这里插入图片描述
发现还是不相同,也就是代表着str[8]没有最长相等前后缀了next[8]=0。

总结

我们需要求的next[i]的值,肯定需要由next[i-1]来确定,因为最长相等前后缀长度中的后缀长度如果大于等于1,那么一定包含前一位str[7:8] = “af”,所以迭代往前回退,再往前回退的过程中,每一次的回退过程都代表着next[i]可能的最大长度。只要回退后的str[next + 1] == str[i]那么,最长相等前后缀的长度也就出来了。由这个结论类推,我们在做匹配的时候,例如str
在这里插入图片描述

我们只需要回退到前一位的最长前后缀长度的位置。
在这里插入图片描述
如果str指针的指向和s指针的指向不相同就接着回退,如果相同那么就接着匹配,因为它代表的是s[2]前面的字符已经匹配过了。

next前缀表实现方法
    void getNext(int *next, const string s) { 
        int j=0; //代表最长相等前后缀的长度
        next[0] = 0; //前缀表首位为0
        int n = s.size();
        for(int i=1; i<n; i++) { //从第二位开始往后更新next数组值
            while(j>0 && s[i]!=s[j]) j = next[j-1];
            if(s[i]==s[j]) j++;
            next[i] = j;
        }
    }
strstr()
    void getNext(int *next, const string s) {
        int j=0;
        next[0] = 0;
        int n = s.size();
        for(int i=1; i<n; i++) {
            while(j>0 && s[i]!=s[j]) j = next[j-1];
            if(s[i]==s[j]) j++;
            next[i] = j;
        }
    }

    int strStr(string haystack, string needle) {
        int next[needle.size()];
        getNext(next, needle);
        int n = haystack.size();
        int j=0; //前缀表中匹配的长度。
        for(int i=0; i<n; i++) {
            while(j>0 && haystack[i]!=needle[j]) j=next[j-1]; //如果匹配不上,那么j所表示的长度也需要回退。
            if(haystack[i] == needle[j]) j++;//按位匹配,如果匹配成功那么就将匹配的长度+1。
            if(j == needle.size()) return i - j + 1; //j等于待匹配字符串的长度时,表示匹配结束。
        }
        return -1;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值