代码随想录第九天 | KMP算法、重复的子字符串

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

文章视频讲解:代码随想录 | 实现strStr()

在母串中寻找子串,最优的算法就是KMP算法,十分经典,也很复杂,笔者从大一程序设计课上初接触到KMP到今天,已经写过4次KMP了,而这一次尝试纯手撕,仍然花了两个小时,最后还是倒在next数组求法上。功不唐捐,希望下一次再见KMP,能直接手撕出来。
以下是个人偏感性理解,仅作参考

KMP更快的原因

暴力匹配就是把子串的头从0号一个一个往前加到m - n号,不匹配就加一。

而KMP不是无脑加一,会像动态规划那样记录,从而实现最优的最远的后移,最大程度减少匹配次数。

KMP像会记住之前已经匹配到了什么作为基业,在遇到挫折之后就不必从头开始,而是吃老本从基业再开始,直到把基业亏光再从头开始或者成功匹配。

KMP怎样记住哪些已经匹配过呢

KMP用到了最长公共前后缀来记录哪些已经匹配过了。代码随想录里的说法是前缀表next[]

什么是最长公共前后缀

一个string的前缀就是除开最后一个元素的所有子串

后缀就是除开第一个元素的所有子串

比如对于aacaab来说:

前缀从长到短有:aacaa,aaca,aac,aa,a

后缀从长到短有:acaab,caab,aab,ab,b

最长公共前后缀就是前后缀里面相同的里面最长那一个

什么是前缀表

个人理解:

对于string来说,它的前缀表肯定和它的长度相同,前缀表的每一个值代表以当前位置结束的子串的最长公共前后缀长度

比如对于串aabaac来说,其前缀表第一位就代表子串a的最长公共前后缀长度,即为0,因为只有一个,去掉第一个或者最后一个之后就没了;

其前缀表第二位就是1,代表子串aa的最长公共前后缀长度,因为去掉头尾之后都是a,而长度为1

所以aabaac的前缀表就为0 1 0 1 2 0

一个特点

如果子串的头尾都不相同,那么其最长公共前后缀长度肯定是0,因为其所有的前缀都有"头",所有的后缀都有"尾",肯定不会相同。

前缀表怎么用呢

之前说到KMP记住之前匹配过哪些,失败之后不用从头开始,而是从"基业"开始,这里的"基业"就是公共前后缀。

如果匹配失败,失败这一位之前都是成功的,将视野缩小到已经匹配上的子串上面,这个子串的后缀是可以匹配上的,那么如果存在相同的前缀,则可以把相同的前缀往前移到后缀的位置,必定仍然匹配,这样就一下子提前了很多位,实现了"保底"

以上是抽象感性说法,下面上严谨代码:

代码

class Solution {
public:
    int strStr(string haystack, string needle) {
        if(haystack.size() < needle.size()) return -1;
        vector<int> prefix = next(needle);
        int m = haystack.size(), n = needle.size();
        int ans = -1, j = 0;
        for(int i = 0;i <= m;) {
            if(j == n) {
                ans = i - n;
                break;
            }
            else if(haystack[i] == needle[j]) {
                j++;
                i++;
                continue;
            }
            else if(j > 0){
                j = prefix[j - 1];//回退吃老本
            }
            else {
                i++;
                j = 0;
            }
        }
        return ans;
    }
    vector<int> next(string s) {
        vector<int> next(s.size(), 0);
        int j = 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[i] = j;
        }
        return next;
    }
};

重复的子字符串

文章视频讲解:代码随想录 | 重复的子字符串

对于前缀表的深入理解和应用,建议打印出测试用例的前缀表,一看便知:重复子串第一次出现地方全是0,后面单调递增1、2、3、4……

代码

class Solution {
public:
    bool repeatedSubstringPattern(string s) {
        vector<int> pre = next(s);
        int len = s.size();
        if(pre[len - 1] != 0 && len % (len - pre[len - 1]) == 0) return true;
        return false;
    }
    vector<int> next(string s) {
        vector<int> ans(s.size(), 0);
        int j = 0;
        for(int i = 1;i < s.size();i++ ){
            while(j > 0 && s[i] != s[j]) j = ans[j - 1];
            if(s[i] == s[j]) j++;
            ans[i] = j;
        }
        return ans;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值