首页 > 世链号 > 【YoBit】字符串:都来看看 KMP 的看家本领!
币圈小鱼儿  

【YoBit】字符串:都来看看 KMP 的看家本领!

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

来源: 代码随想录-程序员 Carl

在一个串中查找是否出现过另一个串,这是 KMP 的看家本领。

题目:28. 实现 strStr()

实现 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 的经典思想就是 :「当出现字符串不匹配时,可以记录一部分之前已经匹配的文本内容,利用这些信息避免从头再去做匹配。」

如果对 KMP 理论基础还不够了解的同学请看 字符串:KMP 是时候上场了(一文读懂系列)

「为了和 字符串:KMP 是时候上场了(一文读懂系列) 字符串命名统一,方便大家理解,以下文章统称 haystack 为文本串 , needle 为模式串。」

都知道使用 KMP 算法,一定要构造 next 数组。

构造 next 数组

我们定义一个函数 getNext 来构建 next 数组,函数参数为指向 next 数组的指针,和一个字符串。代码如下:

 void getNext(int* next, const string& s) 

「构造 next 数组其实就是计算模式串 s,前缀表的过程。」 主要有如下三步:

  1. 初始化

  2. 处理前后缀不相同的情况

  3. 处理前后缀相同的情况

接下来我们详解详解一下。

  1. 初始化:

定义两个指针 i 和 j,j 指向前缀终止位置(严格来说是终止位置减一的位置),i 指向后缀终止位置(与 j 同理)。

然后还要对 next 数组进行初始化赋值,如下:

 int j = -1; next[0] = j; 

j 为什么要初始化为 -1 呢,因为之前说过 前缀表要统一减一的操作,所以 j 初始化为-1。

next[i] 表示 i (包括 i)之前最长相等的前后缀长度(其实就是 j)

所以初始化 next[0] = j 。

  1. 处理前后缀不相同的情况

因为 j 初始化为-1,那么 i 就从 1 开始,进行 s[i] 与 s[j+1] 的比较。

所以遍历模式串 s 的循环下表 i 要从 1 开始,代码如下:

 for(int i = 1; i < s.size(); i++) { 

如果 s[i] 与 s[j+1] 不相同,也就是遇到 前后缀末尾不相同的情况,就要向前回溯。

怎么回溯呢?

next[j] 就是记录着 j (包括 j)之前的子串的相同前后缀的长度。

那么 s[i] 与 s[j+1] 不相同,就要找 j+1 前一个元素在 next 数组里的值(就是 next[j])。

所以,处理前后缀不相同的情况代码如下:

 while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同了 j = next[j]; // 向前回溯 } 
  1. 处理前后缀相同的情况

如果 s[i] 与 s[j + 1] 相同,那么就同时向后移动 i 和 j 说明找到了相同的前后缀,同时还要将 j (前缀的长度)赋给 next[i], 因为 next[i] 要记录相同前后缀的长度。

代码如下:

 if (s[i] == s[j + 1]) { // 找到相同的前后缀 j++; } next[i] = j; 

最后整体构建 next 数组的函数代码如下:

 void getNext(int* next, const string& s){ int j = -1; next[0] = j; for(int i = 1; i < s.size(); i++) { // 注意 i 从 1 开始 while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同了 j = next[j]; // 向前回溯 } if (s[i] == s[j + 1]) { // 找到相同的前后缀 j++; } next[i] = j; // 将 j (前缀的长度)赋给 next[i] } } 

代码构造 next 数组的逻辑流程动画如下:

字符串:都来看看 KMP 的看家本领!

得到了 next 数组之后,就要用这个来做匹配了。

使用 next 数组来做匹配

在文本串 s 里 找是否出现过模式串 t。

定义两个下表 j 指向模式串起始位置,i 指向文本串其实位置。

那么 j 初始值依然为-1,为什么呢?「依然因为 next 数组里记录的起始位置为-1。」

i 就从 0 开始,遍历文本串,代码如下:

 for (int i = 0; i < s.size(); i++) 

接下来就是 s[i] 与 t[j + 1] (因为 j 从-1 开始的) 进行比较。

如果 s[i] 与 t[j + 1] 不相同,j 就要从 next 数组里寻找下一个匹配的位置。

代码如下:

 while(j >= 0 && s[i] != t[j + 1]) { j = next[j]; } 

如果 s[i] 与 t[j + 1] 相同,那么 i 和 j 同时向后移动, 代码如下:

 if (s[i] == t[j + 1]) { j++; // i 的增加在 for 循环里 } 

如何判断在文本串 s 里出现了模式串 t 呢,如果 j 指向了模式串 t 的末尾,那么就说明模式串 t 完全匹配文本串 s 里的某个子串了。

本题要在文本串字符串中找出模式串出现的第一个位置 (从 0 开始),所以返回当前在文本串匹配模式串的位置 i 减去 模式串的长度,就是文本串字符串中出现模式串的第一个位置。

代码如下:

 if (j == (t.size() - 1) ) { return (i - t.size() + 1); } 

那么使用 next 数组,用模式串匹配文本串的整体代码如下:

 int j = -1; // 因为 next 数组里记录的起始位置为-1 for (int i = 0; i < s.size(); i++) { // 注意 i 就从 0 开始 while(j >= 0 && s[i] != t[j + 1]) { // 不匹配 j = next[j]; // j 寻找之前匹配的位置 } if (s[i] == t[j + 1]) { // 匹配,j 和 i 同时向后移动 j++; // i 的增加在 for 循环里 } if (j == (t.size() - 1) ) { // 文本串 s 里出现了模式串 t return (i - t.size() + 1); } } 

此时所有逻辑的代码都已经写出来了,本题整体代码如下:

C++代码

 class Solution { public: void getNext(int* next, const string& s) { int j = -1; next[0] = j; for(int i = 1; i < s.size(); i++) { // 注意 i  1 开始 while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同了 j = next[j]; // 向前回溯 } if (s[i] == s[j + 1]) { // 找到相同的前后缀 j++; } next[i] = j; //  j前缀的长度赋给 next[i] } } int strStr(string haystack, string needle) { if (needle.size() == 0) { return 0; } int next[needle.size()]; getNext(next, needle); int j = -1; // // 因为 next 数组里记录的起始位置为-1 for (int i = 0; i < haystack.size(); i++) { // 注意 i 就从 0 开始 while(j >= 0 && haystack[i] != needle[j + 1]) { // 不匹配 j = next[j]; // j 寻找之前匹配的位置 } if (haystack[i] == needle[j + 1]) { // 匹配j  i 同时向后移动 j++; } if (j == (needle.size() - 1) ) { // 文本串 s 里出现了模式串 t return (i - needle.size() + 1); } } return -1; } }; 

 

免责声明
世链财经作为开放的信息发布平台,所有资讯仅代表作者个人观点,与世链财经无关。如文章、图片、音频或视频出现侵权、违规及其他不当言论,请提供相关材料,发送到:2785592653@qq.com。
风险提示:本站所提供的资讯不代表任何投资暗示。投资有风险,入市须谨慎。
世链粉丝群:提供最新热点新闻,空投糖果、红包等福利,微信:juu3644。