Leetcode[28] 实现-str-str

Leetcode[28] 实现-str-str

实现 strStr()

Category Difficulty Likes Dislikes
algorithms Easy (39.64%) 607 -
Tags

two-pointers | string

Companies

实现 strStr() 函数。

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

示例 1:

输入: haystack = "hello", needle = "ll"
输出: 2

示例 2:

输入: haystack = "aaaaa", needle = "bba"
输出: -1

说明:

needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。

对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。

知识点

KMP算法

简介

KMP算法(快速模式匹配算法),用于解决问题:给定一个主串haystack和一个模式串needle,要求寻找主串中出现的第一个与模式串匹配的子串的开始位置。例如,haystack=“ABCABCE”,needle=“ABCE”,则需要返回3,因为haystack从下标3往后有字串“ABCE” == needle。

真前缀,真后缀

“真前缀”指除了自身以外,一个字符串的全部头部组合。类似的,“真后缀”指除了自身意外,一个字符串的全部尾部组合。

举个例子,对于字符串“abcde”:

扫描二维码关注公众号,回复: 13147811 查看本文章

真前缀:a, ab, abc, abcd

真后缀:e, de, cde, bcde

算法流程

haystack=“BBC ABCDAB ABCDABCDABDE”

needle=“ABCDABD”

(1)首先不断右移needle,尝试去匹配第一个字符
在这里插入图片描述

(2)匹配到第一个字符后,接着继续往后匹配haystack和needle接下来的字符
在这里插入图片描述
(3)直到出现字符不匹配的情况
在这里插入图片描述

此时,比较自然的反应是,将needle整体向后移动一位,在从头开始比较
在这里插入图片描述
(4)KMP算法则通过一些手段,将其直接移动到下图的位置
在这里插入图片描述

怎么做到这一点的呢,这是因为对needle求出了一个next数组,直接将needle移动到了一个前两位已经匹配好的位置上了。

那么,怎样可以确定这个位置呢,不妨关注一下之前匹配失败是什么样的情况:
在这里插入图片描述

由于是在needle[6]这匹配失败,所以已经可以确定,needle[0]~needle[5]已经匹配成功 haystack[4]~haystack[9]。(ABCDAB)

观察到 needle[0]~needle[5]这个串(ABCDAB)有真前缀AB == 后前缀AB,即needle[0]~needle[1] == needle[4]~needle[5]。

根据之前匹配成功,我们知道,needle[4]~need[5] == haystack[8]~haystack[9]。

所以,needle[0]~needle[1] == haystack[8] ~ haystack[9]

因此,我们就可以把needle[0]~needle[1]对准haystack[8] ~haystack[9],直接比较needle[2]和haystack[10]了。
在这里插入图片描述

因此,在这种情况下,我们可以设定next[6] = 2,即当needle[6]匹配失败时,直接用needle[next[6]]——needle[2]去匹配。

即上图中,由于needle[6]——'D’与haystack[10]——’space‘匹配失败,直接用needle[2]——’C‘匹配。

next数组求法

上图中我们设定next[6] = 2,可以发现,也就是needle[0]~needle[5]最长的相同真前后缀的长度。我们已经慢慢接近答案了。

对于needle的首字符needle[0],我们规定next[0] = -1,观察needle[0]~needle[i-1]最长的相同真前后缀的长度,赋给next[i],可以得到:
在这里插入图片描述

先给出这段代码,然后给出讲解。

/* P 为模式串,下标从 0 开始 */
void GetNext(string needle, int next[])
{
    int len = needle.size();
    int i = 0;   // needle的下标
    int j = -1;  
    next[0] = -1;

    while (i < len)
    {
        if (j == -1 || needle[i] == needle[j])
        {
            i++;
            j++;
            next[i] = j;
        }
        else
            j = next[j];
    }
}

i与j的作用

在while循环中,我们会依次计算出next[1],next[2],……即程序中的next[i]。

直到执行到if语句块之后,我们才会给出next[i]的值,并计算下一个next[i]。

假设此时i,j的位置如图所示:
在这里插入图片描述

if-else语句的作用

已有假设已有next[i] == j,即needle[0]~needle[j - 1]匹配needle[i - j]~needle[i - 1]。现在执行if语句:

if needle[i] == needle[j],说明needle[0]~needle[j]匹配needle[i - j]~needle[i],所以next[i + 1] 的值应该是j + 1

else,尝试向前匹配,易知下图绿色标注4个区域是匹配的。令j = next[j],如果下一波needle[next[j]] == needle[i]了,那么needle[0]~needle[next[j]] (即最左边的绿色区域 + 区域右边第一个) 和(最右边的绿色区域+ 区域左边第一个)可以匹配。这就是j = next[j]的原理所在。
在这里插入图片描述

优化next数组

在这里插入图片描述
存在问题,如果needle[5]匹配错误,会拿needle[1]去继续匹配,可是needle[1]还是B…

所以需要优化一下next数组,如果needle[i + 1] == needle[j + 1]的话,继续往前找。

/* P 为模式串,下标从 0 开始 */
void GetNextval(string needle, int nextval[])
{
    int len = needle.size();
    int i = 0;   // P 的下标
    int j = -1;  
    nextval[0] = -1;

    while (i < len)
    {
        if (j == -1 || needle[i] == needle[j])
        {
            i++;
            j++;
          
            if (needle[i] != needle[j])
                nextval[i] = j;
            else
                nextval[i] = nextval[j];  // 既然相同就继续往前找真前缀
        }
        else
            j = nextval[j];
    }
}

题解

KMP模板题

我遇到的坑

vector.size()返回无符号数导致比较错误

在使用next数组进行匹配的时候,刚开始while里的条件直接写成了np < needle.size(),这样的问题是:由于vector.size()返回的是无符号数,当np == -1时,有符号数-1和一个无符号正数比较大小,计算机会将有符号数转成无符号数再比较,计算机将认为-1 > needle.size()而跳出循环,而实际期望的是-1时继续循环。

代码

/*
 * @lc app=leetcode.cn id=28 lang=cpp
 *
 * [28] 实现 strStr()
 */
#include <bits/stdc++.h>
using namespace std;
// @lc code=start
class Solution
{
public:
    int strStr(string haystack, string needle)
    {
        if (needle.size() == 0)
            return 0;
        if (haystack.size() == 0)
            return -1;
        
        int hlen = haystack.size();
        int nlen = needle.size();
        vector<int> next(nlen);
        int j = -1;
        int i = 0;
        next[0] = -1;
        while (i < nlen - 1)
        {
            if (j == -1 || needle[i] == needle[j])
            {
                i++;
                j++;
                if (needle[i] != needle[j])
                {
                    next[i] = j;
                }
                else
                {
                    next[i] = next[j];
                }
            }
            else
            {
                j = next[j];
            }
        }
        int hp = 0, np = 0;
        while (hp < hlen && np < nlen)
        {
            if (np == -1 || needle[np] == haystack[hp])
            {
                hp++;
                np++;
            }
            else
            {
                np = next[np];
            }
        }
        if (np == nlen)
        {
            return hp - np;
        }
        return -1;
    }
};
// @lc code=end

参考
https://segmentfault.com/a/1190000008575379

猜你喜欢

转载自blog.csdn.net/weixin_44458659/article/details/109519535