首先通过一道题目引入KMP,他妈的硬控了我几个小时研究透彻,值得总结总结:
28. 找出字符串中第一个匹配项的下标,这是KMP算法的经典题目,暴力解决一点不难,但用上KMP就值得研究了。
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1 。
示例 1:
输入:haystack = “sadbutsad”, needle = “sad”
输出:0
解释:”sad” 在下标 0 和 6 处匹配。
第一个匹配项的下标是 0 ,所以返回 0 。
示例 2:
输入:haystack = “leetcode”, needle = “leeto”
输出:-1
解释:”leeto” 没有在 “leetcode” 中出现,所以返回 -1 。
KMP的经典思想就是:当出现字符串不匹配时,可以记录一部分之前已经匹配的文本内容,利用这些信息避免从头再去做匹配。
为了记录已经匹配的文本,需要一个next数组,这就是KMP的关键。
而next数组,顾名思义,记录下一个要匹配的元素的位置,本质上就是一个前缀表。
前缀表是用来回退的,它记录了模式串与文本串不匹配的时候,模式串应该从哪里开始重新匹配。
对于本题来说,文本串就是haystack,模式串就是needle。
比如说
文本串:a a b a a b a a f
模式串:a a b a a f
当遍历到第六个,这时文本串是b,模式串是f,发现不相等,这时候模式串只要回退到b来与文本串的b继续比较就好了。
那么怎么确定回退的位置呢?
比如以上情况,怎么回退到b呢?查next数组,发现next数组记录的是2,于是回退到needle[2]。
所以就需要我们在遍历比较之前,先把next数组设置好!!
void getNext(int* next, const string& s)//s是模式串
构造next数组其实就是计算模式串的前缀表的过程。 主要有如下三步:
1.初始化
2.处理前缀相同的情况
3.处理前缀不相同的情况
1.初始化:
要拿当前遍历到的元素和前面的元素做对比,遍历从next[1]开始。因此,必须对next[0]初始化为0;
next[0] = 0; int j=0;//双指针法,j为要插入next的数字。
2.处理前缀相同的情况:
用for(int i = 1; i < s.size(); i++)进行遍历
如果 s[i] 与 s[j ] 相同,找到了j个相同的前后缀,同时还要将j(前缀的长度)赋给next[i], 因为next[i]要记录相同前后缀的长度。
if (s[i] == s[j]) { j++; } next[i] = j;
3.处理前缀不相同的情况:
while s[i] != s[j],需要回退,回退为j = next[j – 1];(这是啥?你先别急)
因为回退到0就不能回退了,还得加上j>0的条件。
关键的关键的关键,就是在于为什么为什么为什么是j=next[j-1]??而且用while来回退多次!!
我刚开始做这道题的时候,我当时啥比了,直接if(s[i]!=s[j]) j=0;
直接令j=0的话,对于一些情况是不可行的:
比如模式串为aabaaac,遍历到第六个(a),a!=b。
但第七个(c)应该继续和b进行比较,因为前面都有两个a。
也就是此时j=2而不是j=0;
实际上求next数组的过程相当于自身去求相同的字符串。
有了next数组,最后实现代码并不难:
class Solution { public: void getNext(int* next, const string& s) { int j = 0; next[0] = 0; for(int i = 1; i < s.size(); i++) { while (j > 0 && s[i] != s[j]) { j = next[j - 1]; } if (s[i] == s[j]) { j++; } next[i] = j; } } int strStr(string haystack, string needle) { if (needle.size() == 0) { return 0; } int next[needle.size()]; getNext(next, needle); int j = 0; for (int i = 0; i < haystack.size(); i++) { while(j > 0 && haystack[i] != needle[j]) { j = next[j - 1]; } if (haystack[i] == needle[j]) { j++; } if (j == needle.size() ) { return (i - needle.size() + 1); } } return -1; } };
另一道可以用KMP的题,不过更简单一些:459.重复的子字符串。
手搓的代码如下:
class Solution { public: void getNext(int* next,const string& s){ next[0]=0; int j=0; for(int i=1;i<s.size();i++){ while(s[i]!=s[j]&&j>0){ j=next[j-1]; } if(s[i]==s[j]){ j++; } next[i]=j; } } bool repeatedSubstringPattern(string s) { if(s.size()==0){ return false; } int next[s.size()]; getNext(next,s); if(next[s.size()-1]>0&&s.size()%(s.size()-next[s.size()-1])==0){ return true; } else{ return false; } } };
暂无评论