Leetcode[28] 实现-str-str
实现 strStr()
Category | Difficulty | Likes | Dislikes |
---|---|---|---|
algorithms | Easy (39.64%) | 607 | - |
实现 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”:
真前缀: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