leetcode刷题记录2021年4月20日

28. 实现 strStr()

实现 strStr() 函数。

给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回 -1 。

说明:
当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与 C 语言的 strstr() 以及 Java 的 indexOf() 定义相符。

示例 1:
输入:haystack = “hello”, needle = “ll”
输出:2

示例 2:
输入:haystack = “aaaaa”, needle = “bba”
输出:-1

示例 3:
输入:haystack = “”, needle = “”
输出:0

虽然标的是简单,但是字符串匹配却有一个挺复杂的优化算法,虽然这题暴力也能过,但是还记记录一下这个优化算法

KMP算法

KMP算法的优化基础是这样的:

class Solution {
public:
    int strStr(string haystack, string needle) {
        int res = -1 + needle.length(),i,j;
        i=0;
        j=0;
        while(i<=haystack.length())
        {
            //cout<<i<<' '<<j<<endl;
            if(j==needle.length())
            {
                res = i;
                break;
            }
            if(i == haystack.length())break;
            if(haystack[i]==needle[j])
            {
                j++;
                i++;
            }else{
                i=i-j;
                i++;
                j=0;
            }
        }
        if(needle == "")res=0;
        return res-needle.length();
};

给主串和子串各一个指针,当指针指向的元素相同,就比较下一个,不同,就把字串指针归零,同时回退主串指针。

KMP算法优化的就是当指针指向的元素不同这一步。
KMP算法的核心是理解next数组,其对应的是子串,含义为next[j]的含义为,j(不包含j)之前的所有的前缀串和后缀串的最大重叠字串的长度。
前缀串指的就是以0元素开头的所有子串。
后缀串是以len-1为结尾的所有子串。

优化后算法

有了next数组,我们就可以将回退i和归零j这两步去掉了,因为重叠子串的关系,我们能够直接给j赋值为next[j],而之所以不回退i,也是因为我们已经将那种抢矿考虑到了。

class Solution {
public:
    int strStr(string haystack, string needle) {
        if(needle.length()==0)return 0;
        vector<int> next(needle.length());
        gennext(next,needle);
        int i=0,j=0;
        int hlen = haystack.length();
        int nlen = needle.length();
        while(i<hlen && j<nlen)
        {
            //cout<<i<<' '<<j<<endl;
            if(j==-1 || haystack[i]==needle[j])
            {
                i++;j++;
            }else{
                j=next[j];
            }
        }
        //cout<<i<<' '<<j<<endl;
        if(j>=needle.length())
            return i-j;
        else
            return -1;
    }
};

next数组的求法

重头戏就是这个,我们设置两个是k与j,j+1代表当前要计算的位置,k代表当前计算中的next值。
首先考虑两个计算情况,j=0、j=1:
j=0 next值没有意义,这里为了计算方便,设为-1
j=1 next值为0;
然后就可以考虑情况了,前面说了k代表计算中的next值,也就是说0~k-1这一段就是重叠子串,而我们要计算的是next[j+1],当j与k指向的元素相等的时候,next[j+1]=k+1,不相等的时候有点难讲,可能画图比较明白,这里推荐这个博文,讲的还算清楚
博文
总体来讲,就是利用现有的性质推导,过程如下:
已知:
k为当前的next值,也就是j之前(不包括j)的最大重叠子串,表示成等式就是 0,k-1 = j-k,j-1
证明:
k = next[k]
首先 next[k]满足如下等式
0,next[k]-1 = k-next[k],k-1
那么可以得出:
0,next[k]-1 = j-next[k],j-1
是不是很眼熟,这就是最大重叠子串的定义,这个式子所代表的k值就是next[k],所以有k=next[k],至于为什么这里的k就是最大值,可以用反证法证明,挺简单的就不说了。
程序如下:

void gennext(vector<int>& next,string sub)
{
    int k=-1,j=0;
    next[0]=-1;
    while(j<sub.length()-1)
    {
        if(k==-1 || sub[k]==sub[j])
        {
            k++;j++;
            next[j]=k;
        }else{
            k = next[k];
        }
    }
}

遗留问题

这里说一下为什么要将next[0]设置为-1,主要是为了处理子串第一个字符与主串就不匹配的情况的,这时候按照前面的程序,j=next[j],i是不变的,但是这里i是应该变的,所以给next[0]赋值-1,打个标记,等到下一次循环把这种情况放在字符串匹配的哪里去处理,这让就完成里需要完成的i++,j不变的操作了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值