代码随想录算法训练营第22天|补第9天的KMP算法,28. 找出字符串中第一个匹配项的下标|459.重复的子字符串

前置知识

文章链接:https://programmercarl.com/0028.实现strStr.html#思路

  • KMP的经典思想就是:当出现字符串不匹配时,可以记录一部分之前已经匹配的文本内容,利用这些信息避免从头再去做匹配。
  • 前缀表:next数组就是一个前缀表(prefix table)。前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。next数组也可以是前缀表加一之后的结果,方便进行代码的实现。

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

题目链接:https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/description/
视频链接:https://www.bilibili.com/video/BV1PD4y1o7nd/

KMP算法:

总结:此题分两部分:先求next数组,接着进行匹配。

  1. 以下next数组的求法中,我将j=0,i=1;并设每一个数组元素表示除这个元素之外的前面的数组的最长公共前后缀
    所以next[0]=-1;next[1]=0是固定的
    关于怎么求next数组推荐:https://www.youtube.com/watch?v=GTJr8OvyEVQ
  2. 在文本串和模式串匹配的过程中,文本串是一个一个的向后移动并进行比较的,所以时间复杂度是n,加上模式串求解next数组的时间复杂度m,所以总的时间复杂度为O(m+n)
class Solution {
public:
    vector<int> getNext(string needle){
        int j=0;
        int len=needle.size();
        vector<int> next;
        //初始化,next前两个元素值是固定的(next元素值表示除它之外前面子数组的最大公共前后缀)
        next.push_back(-1);
        next.push_back(0);
        for(int i=1;i<len-1;i++){
            //当前后缀不相等,j向后回退
            while(j>0&&needle[j]!=needle[i]){
                j=next[j];//回到当前j所指向的next数组元素值对应的下标处
            }
            if(needle[j]==needle[i]) j++;
            next.push_back(j);
        }
        return next;
    }

    int strStr(string haystack, string needle) {
        vector<int> next=getNext(needle);
        int res=-1;
        int lenH=haystack.size();
        int lenN=needle.size();
        int i=0,j=0;
        //出口为,既没有匹配成功,i也到了lenH
        for(i;i<lenH;i++){
            //不匹配,j回退,直到j=0或匹配为止
            while(j>0&&haystack[i]!=needle[j]){
                j=next[j];
            }
            //匹配的时候,j++
            if(haystack[i]==needle[j]) j++;
            //一旦出现了j=lenN,说明匹配成功,此时还没有i++
            if(j==lenN) {
                res=i-j+1;
                return res;
            }
        }
        return res;
    }
};

这里一开始直接赋了两个值,如果用next[1],则会导致数组越界(因为没有考虑needle为单个字符的情况),如果要用可以作如下修改:

 vector<int> getNext(string needle){
        int j=0;
        int len=needle.size();
        vector<int> next(len);
        //初始化,next前两个元素值是固定的(next元素值表示除它之外前面子数组的最大公共前后缀)
        next[0]=-1; //之所以这个可以定义,是因为len至少为1,所以可以取到next[0]
        //next[1]=0;  不可以这样写是因为如果len为1,next[1]是取不到的,会越界
        if(len>1) next[1]=0;
        for(int i=1;i<len-1;i++){
            //当前后缀不相等,j向后回退
            while(j>0&&needle[j]!=needle[i]){
                j=next[j];//回到当前j所指向的next数组元素值对应的下标处
            }
            if(needle[j]==needle[i]) j++;
            next[i+1]=j;
        }
        return next;
    }

459.重复的子字符串

文章链接:https://programmercarl.com/0459.重复的子字符串.html#算法公开课
视频链接:https://www.bilibili.com/video/BV1cg41127fw
题目链接:https://leetcode.cn/problems/repeated-substring-pattern/description/

方法一:移动匹配
s + s ,并刨除 s + s 的首字符和尾字符,这样避免在s+s中搜索出原来的s,我们要搜索的是中间拼接出来的s。
方法二:KMP算法
要点:最长前后缀剩余部分即可能为最小重复子串(剩余部分不可比s的一半的长度还大)
另外,如果剩余部分不可整除s,则说明也没有最小重复子串
例如:

由于这个题在解决的函数中需要用到前缀表,所以对于右移一位的next数组,最好不右移,直接使用前缀表表示的next数组。

class Solution {
public:
    vector<int> getNext(string s){
        int j=0,i=1;
        vector<int> next;
        //初始化next数组的头元素值(固定的)
        next.push_back(0);
        for(int i=1;i<s.size();i++){
            //不相等
            while(j>0&&s[i]!=s[j]){
                j=next[j-1];
            }
            if(s[i]==s[j]) j++;
            next.push_back(j);
        }
        return next;
    }
    bool repeatedSubstringPattern(string s) {
        vector<int> next=getNext(s);
        int size=next.size();
        int remain=size-next[size-1];
        //如果没有公共前后缀,或remain整除s,则false
        if(next[size-1]==0||size%remain!=0) return false;
        return true;
    }
};
posted @   W-Vicky11  阅读(217)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端
点击右上角即可分享
微信分享提示