代码随想录训练营day09打卡

本文介绍了如何使用KMP算法(Knuth-Morris-Pratt算法)在给定的haystack字符串中查找needle子串的第一个匹配项下标,通过构造前缀表优化暴力枚举法,时间复杂度降低。同时提及了如何通过KMP算法检测重复子字符串的存在。
摘要由CSDN通过智能技术生成

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

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

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

如果使用暴力枚举,即用i遍历haystack数组,每次从i的位置开始比较,用j指针needle字符串,如果匹配失败就将j回退到neddle字符串开头,并且将i++,这种做法的时间复杂度为O(m*n),其中m是haystack的长度,n是needle的长度。

本题我们使用的是KMP算法实现模式串匹配判断。通过寻找needle和的最长相等前缀,每次匹配失败时,不直接将j指针回退到开头,而是通过前缀表不断递归回退到相等前缀的地方,判断当前字符是否相等,即先判断当前不相等位置的前面串是否存在与后缀相等的前缀,如果存在,则跳转到该串位置,再将haystack[i]与后一个位置neddle[j+1]进行比较,如果不相等,则继续跳转到与neddle[j+1]前面串后缀相等的前缀地方继续进行匹配

while (j>=0 && needle[j+1] != haystack[i]) 
    {
        j = next[j];
    }

如果相等,从当前位置继续遍历即可。而这个需要回退的地方我们使用前缀表记录,即有多大最长长度的相等前后缀(前缀是指不包含最后一个字符且包含第一个字符的连续字符串,后缀是指不包含第一个字符且包含最后一个字符的连续字符串)。如果最后我们的j指针指向了needle数组最后的位置,即每个字符都匹配上了,说明haystack中存在模式串needle。

for (int i = 0; i < haystack.size(); i++)
{
    while (j>=0 && needle[j+1] != haystack[i]) 
    {
        j = next[j];
    }
    if (needle[j + 1] == haystack[i])
    {
        j++;
    }
    if (j == needle.size() - 1)
    {
        return i - j ;
    }
}

那么问题的关键就是如何求得前缀表,这里我们使用的前缀表next[j]是保存的需要跳转的j的位置,而需要比较的是跳转位置后面的位置,因此我们将j的初值和next[0]设置为-1,即当跳到j=-1时,我们比较j+1下标的元素也就是needle[0]。next[j]的后续元素实现方法和模式串匹配类似,我们从下标i=1开始,依次比较前后缀,如果相等,就将j++,然后将j赋值给next[i],说明这一段存在长度为j+1的最长相等前后缀,然后i后移,从之前的最长相等前后缀进行比较,如果当前元素(也就是前面子串的后缀的后一个位置)不同,则回退到前缀,比较前缀的后一个位置,如果依然不同,则将该前缀看成后缀,寻找该前缀的相等前缀(因此这个过程是递归的),最后j指针回退到-1或者能匹配上的第一个前缀末尾。如果匹配上,将j++,此时再将j的位置赋给next[i]。

       int j=-1;
       vector<int> next(needle.size());// abababab
       next[0] = -1;
       for (int i = 1; i < needle.size(); i++)
       {
           while (j >= 0 && needle[j + 1] != needle[i])
           {
               j = next[j];
           }
           if (needle[j + 1] == needle[i])
           {
               j++;
           }
           next[i] = j;
       }

完整代码:

class Solution {
public:
    int strStr(string haystack, string needle) {//needle是子串
        int j=-1;
        vector<int> next(needle.size());// abababab
        next[0] = -1;
        for (int i = 1; i < needle.size(); i++)
        {
            while (j >= 0 && needle[j + 1] != needle[i])
            {
                j = next[j];
            }
            if (needle[j + 1] == needle[i])
            {
                j++;
            }
            next[i] = j;
        }
        for(int i=0;i<next.size();i++)
        cout << next[i];
        j = -1;
        for (int i = 0; i < haystack.size(); i++)
        {
            while (j>=0 && needle[j+1] != haystack[i]) 
            {
                j = next[j];
            }
            if (needle[j + 1] == haystack[i])
            {
                j++;
            }
            if (j == needle.size() - 1)
            {
                return i - j ;
            }
        }
        return -1;
    }
};

重复的子字符串

459. 重复的子字符串

给定一个非空的字符串 s ,检查是否可以通过由它的一个子串重复多次构成。

KMP算法:通过求得s字符串的next数组,s字符串的长度-s字符串的最大相等前后缀(即next[s.size()-1]的值)所剩下的子串就是要求得的重复子串。如果该子串长度能被s字符串长度整除,说明能由它的一个子串重复多次构成。

假设s="ababababababab"

那么a="ababababababxx"

那么b="xxabababababab"

证明过程:a是相等前缀,b是相等后缀,x表示该位置为空。a[0],a[1]=b[0],b[1]=a[2],a[3]=b[2],b[3]=...=a[s.size-2],a[s.size-3]

代码如下:注意结尾判断条件,当最长相等前后缀长度为0时也应该判为false。

class Solution {
public:
    bool repeatedSubstringPattern(string s) {
        int j = -1;
        vector<int> next(s.size());// abababab
        next[0] = -1;
        for (int i = 1; i < s.size(); i++)
        {
            while (j >= 0 && s[j + 1] != s[i])
            {
                j = next[j];
            }
            if (s[j + 1] == s[i])
            {
                j++;
            }
            next[i] = j;
        }
        for (int i = 0; i < next.size(); i++)
            cout << next[i];
        return next[s.size() - 1] != -1 && s.size() % (s.size() - next[s.size() - 1] - 1) == 0;
    }
};

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值