[LeetCode]28 实现子字符串匹配

Implement strStr() (实现子字符串匹配)

【难度:Easy or Medium】
Implement strStr().

Returns the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack.
返回在主字符串中出现的第一个子字符串的下标,如果不存在返回-1。


解题思路

经典的字符串匹配算法,可采用暴力匹配(时间复杂度为O(m*n))或KMP算法(时间复杂度为O(m+n)),这里主要介绍KMP算法。
当暴力匹配算法在第i个字符的位置遇到下面的情况(C和D不匹配)时,它采取的仍然是向前移动一位重新开始匹配:
这里写图片描述
这样没有考虑到前面i-1个字符中存在重复的部分AB,长度k=2。
那么对于KMP算法,它则是直接向前移动i-1-k=6-2=4个位置开始匹配,因为前i-1个字符已经匹配过,而AB是重复的,这样就消除了冗余匹配的过程:
这里写图片描述
那么一个重要的问题就是如何得知是否可以跳过前面已匹配的的格子,并且要跳过多少个格子?
所谓跳过几个格子,意思就是匹配到当前字符,如果不能成功匹配,那么可以通过之前匹配过程记录的数据,来知道前面有几个字符是与子字符串的开头完全匹配的,可以直接开头移动到这个当前匹配的地方。

比如:
这里写图片描述
在黑色框之中我们可以看到当前匹配字符D前存在两个字符AB是与开头完全匹配的,那么当D与C不能匹配时,下次匹配就是直接从中间的黑色框开始,可以直接移动的格子数为:
D的下标-黑框中字符的个数=6-2=4个。
接着,我们会发现现在所缺少的信息就是黑框中字符的个数,观察图片,我们可以发现该信息只与子字符串(要匹配的字符串)有关,所以只需在子字符串中操作。

当我们匹配到D的时候,D前面的AB肯定已经匹配了,那么如果D不匹配,D前面的AB与字符串开头AB相同,就可以移动开头过来。将该情况抽象为:
当我们匹配到第i个字符(下标为i-1)时,如果他不匹配,并且该字符前面的k个字符与字符串的最前面的k个字符是相同的,那么我们就可以直接把子字符串向前移动(i-1-k)个格子,注意k < i-1,因为k==i-1时相当于没有移动。

所以问题又变为如何求子字符串中每个位置的k值。
这里写图片描述
蓝色箭头表示从头匹配到哪个字符,红色箭头表示当前扫描到的字符。
第1个字符不需要匹配,因为它前面没有字符,且由于k<i-1那么其k值为-1;
接下来扫描第2个字符B,因为它前面只有1个字符A,且A != B,没有重复部分,所以k=0;
对于第3个字符C:
这里写图片描述
查看它前一个字符B是否与当前蓝色箭头t指向的字符相同,如果相同t++,C的k值等于自增之后的t,如果不相等,k=0,蓝色箭头重置为第一个字符A。
第4个和第5个字符D、A结果都一样k=0,t=0;
那么到了第6个字符B:
这里写图片描述
B前一个字符A与蓝色箭头指向的字符相等,那么蓝色箭头向右移动一格,t++,k=1;
第7个字符D:
这里写图片描述
D前一个字符B与蓝色箭头指向的字符相等,那么蓝色箭头向右移动一格,t++,k=2;
假设后面还有字符,如:
这里写图片描述
E前一个字符V与蓝色箭头指向的字符不相等,那么蓝色箭头重置为第一个字符,t=0,k=0,于是变为:
这里写图片描述
整体思路大致如此。


c++代码如下:

class Solution {
public:
    int strStr(string haystack, string needle) {
        if (haystack.empty() && needle.empty())
            return 0;
        if (needle.size() == 0)
            return 0;
        if (haystack.size() < needle.size())
            return -1;

        return KMP(haystack,needle);
    }
    //求子字符串的k值
    vector<int> getNext(string tmp) {
        int i = tmp.size();
        int j = 0;
        int k = -1;
        vector<int> ans(i);
        ans[0] = -1;
        while (j < i-1) {
            if (k == -1 || tmp[j] == tmp[k]) {
                k++;
                j++;
                ans[j] = k;
            } else {
                k = ans[k];
            }
        }
        return ans;
    }
    int KMP(string s, string sub) {
        int i = 0;
        vector<int> next = getNext(sub);
        int j = 0;
        while (i < s.size()) {
            if (j == -1 || s[i] == sub[j]) {
                i++;
                j++;
            } else {
                j = next[j];
            }
            if (j == sub.size())
                return i-j;
        }
        return -1;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值