kmp算法
描述
给你一个文本串 T ,一个非空模板串 S ,问 S 在 T 中出现了多少次
数据范围:
,%5Cleq1000000)
要求:空间复杂度
,时间复杂度 %2Blen(T)))
给你一个文本串 T ,一个非空模板串 S ,问 S 在 T 中出现了多少次
数据范围:
要求:空间复杂度
方法一
思路分析
这是典型的字符串模式匹配问题,方法一首先使用暴力算法,即S中的字符与T中的字符一个一个进行比较,当T中以第i个字符开始的字符串无法与S进行完全配对时,就继续从i+1个字符开始配对,而此时S从开始位置进行匹配。这种方法易于理解,但是时间复杂度很高,因此代码因超时不通过。
图解
核心代码
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 计算模板串S在文本串T中出现了多少次
* @param S string字符串 模板串
* @param T string字符串 文本串
* @return int整型
*/
int BFMatching(string s1, string s2, int pos)
{
int len1 = s1.length();
int len2 = s2.length();
if(len2 > len1 || !len2) return -1;
int i = pos, j = 0;
while(i<len1 && j<len2){
if(s1[i] == s2[j]){
j++;i++;//当前字符匹配成功,各自向后
}
else{
i = i-j+1;j = 0;//如果失败模式串从开始位置出发,主串移动下一位置
}
}
if(j==len2) return i-j+1;
return -1;
}
int kmp(string S, string T) {
// write code here
int ss=S.size();//模板串
int tt=T.size();//文本串
int count=0;
int k=0;
while(k<tt-ss)
{
int index=BFMatching(T, S, k);//返回匹配到的开始位置
if(index!=-1)
{
count++;
k=index;
}
}
return count;
}
};
复杂度分析
- 时间复杂度:暴力匹配算法将两个字符串的字符一一比较,时间复杂度为$O(len(S)len(T))$,len(T),len(S)分别表示主串与模式串的长度
- 空间复杂度:该算法的空间复杂度为$O(len(S))$
方法二
思路分析
分析方法一中的算法,可以给我们一些有用的信息。方法一中,暴力解法在每一次配对失败后,都会从T中的下一个字符开始与S中的字符开始匹配,每次匹配失败后并没有对下一次的匹配产生帮助。因此引入KMP算法,KMP算法是一种改进的字符串匹配算法,KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。要移动的位数我们把它定义成一个数组,这个数组就称之为next数组,而next数组的值是代表着字符串的前缀与后缀相同的最大长度(不能包括自身)。“前缀"指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。
具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。举例如下:
具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。举例如下:
要想查找最大前后缀,在每次匹配之后都要重新“从头”开始查找,然而,当我们前面已经匹配成功k个字符时,第$k+1$个字符匹配失败了,那么我们需要将模式串从头开始重新匹配,但是如果我们细心点我们可以发现,前面$k$个字符同样包含最大前后缀,因此令$j=next[j-1]$实质上就是将模式串回溯到前面$k$个字符所拥有的最大前后缀的位置上也就是说在求解next[j]时,必然包含前面最大前后缀长度的,因此先回溯到前面的最大前后缀的位置上,然后比较新增加的字符。
图解:
核心代码
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 计算模板串S在文本串T中出现了多少次
* @param S string字符串 模板串
* @param T string字符串 文本串
* @return int整型
*/
vector<int> Next;
void next_function(string S)
{
Next.push_back(-1);
int j=0;
for(int i=1;i<S.size();i++)
{
while(j>0&&S[j]!=S[i])//前缀与后缀
{
j=Next[j-1];
}
if(S[j]==S[i])
j++;
Next.push_back(j);
}
}
int kmp(string S, string T) {
// write code here
int ss=S.size();//模板串
int tt=T.size();//文本串
int count=0;
int j=0;
next_function(S);//计算next数组
for(int i=0;i<tt;i++)
{
while(j>0&&S[j]!=T[i])
{
j=Next[j-1];//如果测试的两个字符不相等,i不动,j变为当前测试字符串的next值
}
if(S[j]==T[i])
j++;////S[i]==T[j],对应位置字符相等指向当前测试的两个字符串下标i和j都向后移
if(j==ss)
{
count++;
j=Next[j-1];//继续向后移动寻找,j变为当前测试字符串的next值
}
}
return count;
}
};
复杂度分析
- 时间复杂度:需要预先处理得到next数组,时间为$O(len(S))$,然后遍历一次主串,时间复杂度为$O(len(T))$,总的时间复杂度为$O(len(S)+len(T))$
- 空间复杂度:需要设置一个next数组,空间复杂度为$O(len(S))$

京公网安备 11010502036488号