LeetCode28 找出字符串中第一个匹配项的下标

        虽然是一道简单题,但是这道题涉及到了KMP算法,值得新手利用本题进行学习和掌握KMP算法的本质,在了解本质的基础上背板。

先介绍一下朴素的解法(摘自LeetCode大佬 宫水三叶)

class Solution {
public:
    int strStr(string s, string p) {
        int n = s.size(), m = p.size();
        for(int i = 0; i <= n - m; i++){
            int j = i, k = 0; 
            while(k < m and s[j] == p[k]){
                j++;
                k++;
            }
            if(k == m) return i;
        }
        return -1;
    }
};

作者:宫水三叶
链接:https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

        基本上大家第一次做基本都是这个思路,但是这个方法在不考虑剪枝的情况下时间复杂度是O(m*n),而通过KMP算法可以将时间复杂度降低到O(m+n)。

        首先先来了解一下什么是KMP算法?

        KMP 算法是一个快速查找匹配串的算法,它的作用其实就是本题问题:如何快速在「原字符串」中找到「匹配字符串」。通过将「非完全匹配」的过程中提取到的有效信息加以复用,达到减少「重复匹配」的目的。具体介绍也可以参考:(52 封私信 / 80 条消息) 如何更好地理解和掌握 KMP 算法? - 知乎 (zhihu.com)

        KMP算法的关键是解决前缀表next数组有何作用和如何实现。

        关于作用:利用前缀表,将模式串各个字串的最大相同前后缀数量标示出来,在匹配过程中遇到不匹配的情况,可以利用最大相同前后缀数量,省略很多不必要的重复匹配过程(跳过不可能匹配成功的匹配过程)。

        关于如何实现:接下来关于KMP匹配过程的介绍内容都摘自宫水三叶大佬的题解内容

然后我们再看看「KMP 匹配」过程:
首先匹配串会检查之前已经匹配成功的部分中里是否存在相同的「前缀」和「后缀」。如果存在,则跳转到「前缀」的下一个位置继续往下匹配:

跳转到下一匹配位置后,尝试匹配,发现两个指针的字符对不上,并且此时匹配串指针前面不存在相同的「前缀」和「后缀」,这时候只能回到匹配串的起始位置重新开始:

了解了实现流程,那么接下来介绍具体如何构建next数组,可参考代码随想录:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

        首先我们要了解的是构建next数组只和匹配字符串有关,所以我们也只需要编写和匹配字符串有关的代码。

代码如下:

        vector<int> next(m);//构造了一个int类型的数组,长度是m,m指匹配字符串的长度
        //初始化next数组
        next[0]=0;
        for(int i=1,j=0;i<m;i++){
            //j在这标识已经该子串的最大前后缀的前缀的末尾位置
            while(j&&p[i]!=p[j]) j=next[j-1];
            //代码含义是:一旦遇到不相等的情况,即无法构成更长的前后缀时,
            //则回退到字串j-1的情况,再次判断是否相等,相等则意味着可以
            //构成更长的前后缀,不相等重复操作,直到相等或j=0,退出循环。
            if(p[i]==p[j]){
                next[i]=j+1;
                //相等意味着可以构成更长的前后缀,最大长度是下标j+1
                j++;
                //和i一起递增指向下一组比较对象
            }else{
                //如果j=0并且p[i]!=p[j]则最长前后缀的长度为0
                next[i]=0;
            }
        }
        

然后是完整的代码

class Solution {
public:
    int strStr(string s, string p) {
        int n=s.size(),m=p.size();
        if(m==0) return 0;
        else if(m>n) return -1;
        vector<int> next(m);
        //初始化next数组
        next[0]=0;
        for(int i=1,j=0;i<m;i++){
            while(j&&p[i]!=p[j]) j=next[j-1];
            if(p[i]==p[j]){
                next[i]=j+1;
                j++;
            }else{
                next[i]=0;
            }
        }
        //匹配串p和目标串s匹配的过程
        for(int i=0,j=0;i<n;i++){
            while(j&&s[i]!=p[j]){
                j=next[j-1];
                //j在这标识已经匹配的字符的末尾位置
            }
            if(s[i]==p[j]) {
                j++;
            }
            if(j==m) return i-m+1;
            //当匹配的数量等于匹配串p的长度的时候,即完全匹配,返回在目标串中的完全匹配串的下标
        }
        return -1;
    }
};

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值