今天的主要任务就是搞懂KMP算法!
28. 实现 strStr()
题目链接/文章讲解/视频讲解: 代码随想录
void getNext(int* next, const string& s) { //构造next数组
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算法,它的核心就在于next数组,当出现字符串不匹配时,可以记录一部分之前已经匹配的文本内容,利用这些信息避免从头再去做匹配。next数组记录的是当不匹配时,模式串该从哪一位来继续匹配,从而省去了从头开始匹配的麻烦。
关于如何求解next数组,这篇博客写的相当通俗易懂了,十分清晰:KMP算法的前缀next数组最通俗的解释,如果看不懂我也没辙了-CSDN博客
在求完next数组之后了,就可以遍历进行字符串匹配了。定义两个下标j指向模式串起始位置,i指向文本串起始位置。然后若两字母相等则依次向后遍历,若不相等,则需要将j指向next[j-1],再次进行比较,即将模式串的下标转到下一个匹配的位置进行匹配。若j指向了模式串的结尾,则说明文本串中存在能匹配模式串的字符串,则返回该起点位置,否则返回-1。
459.重复的子字符串
题目链接/文章讲解/视频讲解: 代码随想录
void getNext (int* next, const string& s){
next[0] = 0;
int j = 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;
}
}
bool repeatedSubstringPattern (string s) {
if (s.size() == 0) {
return false;
}
int next[s.size()];
getNext(next, s);
int len = s.size();
if (next[len - 1] != 0 && len % (len - (next[len - 1] )) == 0) {
return true;
}
return false;
}
这道题也是用了KMP的思想,首先先求出next数组。其次呢,
假设字符串s使用多个重复子串构成(这个子串是最小重复单位),重复出现的子字符串长度是x,所以s是由n * x组成。
因为字符串s的最长相同前后缀的长度一定是不包含s本身,所以 最长相同前后缀长度必然是m * x,而且 n - m = 1,(这里如果不懂,看上面的推理)
所以如果 nx % (n - m)x = 0,就可以判定有重复出现的子字符串。
通俗来说,就是如果该字符串是由重复子串构成的话,一定是由它末尾的那个重复子串(也就是最长相等前后缀不相等的那部分)重复了两次或多次才形成的,这样的话在next数组中的体现就是next[末尾字符] = 结尾重复子串的开头,如果满足这样就能由子串来形成整个字符串了。
具体推导还是看代码随想录上的文字版吧,解释的十分清晰。