之前教练讲过kmp算法所以比较熟悉,但当时没有掌握的很熟练,只是知道大概原理,今天比较细致的刷了一遍,还是很有收获的。
首先kmp算法就是在匹配字符串时效率化,正常的暴力法是两个for循环,一旦模式串和文本串不匹配,就要从文本串的下一个点开始,(模式串重新开始)匹配,费时间。kmp算法的关键就是,在匹配不成功的时候,模式串可以不重新开始,直接从之前记录的点开始(具有跳到之前已经匹配过的地方的能力)。那凭什么能找到之前记录的那个点呢?就是靠的最长相等前后缀,因为找到了最长相等的前缀和后缀,匹配失败的位置是后缀子串的后面,那么我们找到与其相同的前缀的后面重新匹配就好了,(前缀重复的部分就可以省略,这就是效率化)。 那怎么记录最长相等前后缀,全靠前缀表,我们在这里用next存储,先给大家一张图片
(前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串;后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。)
长度为前1个字符的子串a
,最长相同前后缀的长度为0。
长度为前2个字符的子串aa
,最长相同前后缀的长度为1。
长度为前3个字符的子串aab
,最长相同前后缀的长度为0。
长度为前4个字符的子串aaba
,最长相同前后缀的长度为1。
长度为前5个字符的子串aabaa
,最长相同前后缀的长度为2。
长度为前6个字符的子串aabaaf
,最长相同前后缀的长度为0。
可以看出模式串与前缀表对应位置的数字表示的就是:下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。
找到的不匹配的位置, 那么此时我们要看它的前一个字符的前缀表的数值是多少。为什么要前一个字符的前缀表的数值呢,(此时指针所指的位置已经不匹配成功了)因为要找前面字符串的最长相同的前缀和后缀。所以要看前一位的 前缀表的数值。前一个字符的前缀表的数值是2, 所以把下标移动到下标2的位置继续比配。 最后就在文本串中找到了和模式串匹配的子串了。next的储存方式有好多种:我这里默认取的是前缀表不减一。
Leetcode 28. 找出字符串中第一个匹配项的下标
题目链接 28 找出字符串中第一个匹配项的下标
上代码:
class Solution {
public:
void getNext(int *next,const string &s){//对模式串进行求next前缀表的操作,这里是为了使用前缀表方便告诉我们匹配失败之后,
//我们应该跳到哪里去重新匹配
int j = 0;//这里采用的前缀表是不减一的形式,而是右移的情况
next[0] = 0;//初始化
//这里是用来求最长相等前后缀
for(int i=1;i<s.size();i++){//i指针起遍历作用,不停的往前
while(j>0&&s[i]!=s[j]){//j等于0时无法前移,如果前缀和后缀字母不同,并且j不是首元素,就将j等于前一个的next值
j = next[j-1];//next[j-1]就是记录着j,包括j之前的子串的相同前后缀的长度
}
if(s[i]==s[j]){//如果相等j指针就右移
j++;
}
next[i] = j;//将j赋值给next,此时j记录着当前i的子串的相同前后缀的长度
}
}
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) ;//返回haystack中的位置
}
}
return -1;
}
};
Leetcode 459. 重复的子字符串
题目链接 459 重复的子字符串
两种方法:
第一种 移动匹配法,这个我觉得是非常难想的,给个图:
一个字符串s:abcabc,如果满足题目要求的话,必须有前后相同的子串组成,那么既然前面有相同的子串,后面有相同的子串,用 s + s,这样组成的字符串中,后面的子串做前串,前面的子串做后串,就一定还能组成一个s。(所以判断字符串s是否由重复子串组成,只要两个s拼接在一起,里面还出现一个s的话,就说明是由重复子串组成。)
当然,我们在判断 s + s 拼接的字符串里是否出现一个s的的时候,要刨除 s + s 的首字符和尾字符,这样避免在s+s中搜索出原来的s,我们要搜索的是中间拼接出来的s。
下面上代码:
class Solution {
public:
bool repeatedSubstringPattern(string s) {
string ss = s+s;
ss.erase(ss.begin());
ss.erase(ss.end()-1);//c.end();返回指向容器最后一个数据单元+1的指针
if(ss.find(s)!=std::string::npos){ //npos 是一个常数,用来表示不存在的位置
//这里的if语句意义:没有发现不存在,即发现了s
return true;
}
return false;
}
};
};
kmp法