找出字符串中第一个字符的下标
思路:本题是一道使用kmp算法的经典题,实际上本题所要作的就是字符串的模式匹配,给定一个文本串,一个模板串,要在文本串中找出第一个模板串的位置,并返回其中第一个字符的下标。那么,这道题目如果用两层for循环这样的暴力方式求解的话,时间复杂度是O(M*N),如果使用KMP算法的话,就可以避免很多无意义的比较,它的时间复杂度只有O(M+N)。
KMP算法实现的关键是要使用next数组,那么next数组存放的内容其实就相当于是一个前缀表,这个表要怎么求出来呢,主要分四步走:
1、初始化(将前缀末尾用j来表示,初始值设为0,它也代表着最长相等前后缀长度,将后缀末尾用i来表示,它将在for循环中进行初始化(注意其初始值应为1,这是前后缀的定义决定的),如果匹配在第一个字符就失败,无疑要重头匹配,因此next[0]=0)
2、处理前后缀不相同的情况(注意当前后缀不相同时,j要往后回退多少,要看前一个字符在next数组中的值,因此j>0才可以)
3、处理前后缀相同的情况(j++)
4、更新next数组
封装好这个构造next数组的函数后,这道题的实现就比较容易了。
步骤:
1、判断传入模板串的合法性,如果模板串的长度为0,那么返回0即可。
2、开辟next数组,大小同模板串相同
3、接着调用构造next数组的函数,将next数组求出来
4、写一个for循环去比较文本串和模板串,用next数组中存储的值来避免无意义的比较,同样的:
(1)如果j>0且文本串和模式串字符不相同,那么就让指向模式串的指针回退,回退的依据就是next数组
(2)如果文本串和模式串的字符相同,当然让j++,继续下一轮比较,i++在for循环中进行
(3)如果j的值等于模式串的尺寸,那么说明匹配成功,返回文本串中第一个字符的下标。
下标的求法:文本串的指针i-模式串的长度加1(可以通过简单情况来验证,例如文本串aa,模式串a,0-1+1=0,结果正确)
总结:KMP算法的关键是要求出next数组,在构造next数组的过程中,一定要分清情况,理清步骤,由于该算法的时间复杂度提升很明显,我觉得还是很值得学习的。
代码:
class Solution {
public:
void getNext(string &needle,int* next){
//初始化
int j=0;
next[0]=0;
//j指向前缀末尾 i指向后缀末尾
//因此j从0开始 i从1开始
for(int i=1;i<needle.size();i++){
//处理前后缀不相同的情况
while(j>0&&needle[i]!=needle[j]){
j=next[j-1];
}
//处理前后缀相同的情况
if(needle[i]==needle[j]){
j++;
}
//更新next
next[i]=j;
}
}
int strStr(string haystack, string needle) {
if(needle.size()==0){return 0;}
int next[needle.size()];//开辟空间
getNext(needle,next);
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++;//i++在for循环中
}
if(j==needle.size()){
//匹配成功,返回下标,注意文本串下标从0开始
return i-needle.size()+1;
}
}
return -1;
}
};
力扣459、重复的子字符串
思路:本题可以通过暴力的方法求解,第一层制造字串,第二层比较,时间复杂度是O(N^2),这里采用效率更高的移动匹配的方式来做。
移动匹配的原理是这样的,假设一个字符串含有重复子字符串,那么如果我将该字符串的末尾再加上其本身构成一个新的字符串的话,那么第一个原字符串的后半部分和第二个原字符串的前半部分应该能够组成原字符串,否则原字符串就不是一个重复字串。
注意:在查询的时候不要忘记将新字符串掐头去尾,否则就相当于直接检索第一个原字符串,就没有意义了。
掐头去尾的时间复杂度是O(n),去寻找一个字符串中是否有字串,这个算法的时间复杂度在上道题目中分析过,是O(M
+N),因此本题的时间复杂度是O(N)。(注意新字符串的由来)
总结:KMP算法可以高效率地查询一个字符串中是否出现过某个子串,重复的子字符串用上KMP算法再加一个拼接的小技巧就可以解决了。
代码:
class Solution {
public:
bool repeatedSubstringPattern(string s) {
//移动匹配
//时间复杂度O(M+N)
string t=s+s;
t.erase(t.begin());
t.erase(t.end()-1);//尾后-1
if(t.find(s)!=std::string::npos){
return true;
}
return false;
}
};