关于next数组最好能结合图片来理解,有很多相关的博客,这里不再引述,本文只总结核心的内容。此外next数组有两个版本,本文用的是next[0]=-1的版本,两个版本没有本质区别,选择一个记忆即可,但选了一个后就把另一个完全忘掉吧,否则容易混淆。
假设有一个字符串str,它的字符元素下标从0记到str.size()-1. next数组是一个能记录str前缀和后缀关系的数组,它的元素next[i]表示:str中,除了第i个字符本身以外,第i个字符前面的这个子串(共包含0~i-1这i个字符)中前缀与后缀的最大重叠长度。 同时,它也表示模板串在第i个字符不匹配时,j下标应该往前回溯到第几个字符处。这两者是等价的。
(前缀是指不包括最后一个字符且包含第一个字符的所有连续字符的组合(我自己的定义,可能有点绕口),比如字符串 aab,它的前缀有两个:a和aa. 后缀的定义类似。)
next数组的求解很简单,通过观察前缀和后缀的重叠长度可以从前往后一个个口算,用代码写也非常简洁:
next[0] = -1;
int i = 0,j = -1;
while (i
注意next数组的元素个数比字符串长度还要多一个,因为最后next[str.size()]表示整个str的前后缀重合度。
给出一个母串a,和一个模板串p,寻找a中能匹配到p的所有位置。
当模板串的第j个字符与母串的第i个字符不匹配时,将j移动到next[j]处即可,把绿色区域对齐。
从匹配过程也可以反过来讨论一下next数组。比如说,当j=0时出现不匹配,即模板字符串的第一个字符就和母字符串第i个字符不匹配时,j无法再往前移动了,而i需要往后移动1位,所以next[0]=-1; 同理,若在j=1时才不匹配,则j需要往前移动1位到第0个字符,即next[1]=0.
KMP字符串匹配代码:https://blog.csdn.net/hujian_/article/details/51475353
vector matchStr(const string& a, const string& p) {
vector next = getNext(p),result;
int i=0, j=0;//母串下标i,模板串下标j.
while (i < a.size()) {
if (j == -1 || a[i] == p[j]) i++, j++;
else j = next[j];
if (j == p.size()) {
//找到一个匹配位置,输出并继续寻找下一个匹配位置。
result.push_back(i - p.size());
j=next[j];
}
}
return result;
}
给出一个由某个循环节构成的字符串,要你找出最小的循环节,例如 abababab 最小循环节当是 ab ,而类似 abab 也可以成为它的循环节,但并非最短。先直接上结论:一个字符串的最小循环节为:len-next[len]. 具体证明过程可以用下面两幅图简略的表达一下,其中第一幅图表示next[len]
再重述一下结论,对于一个长度为len的字符串str, 它的循环单元是T=len-next[len]。也就是说,str的字符总会以T为周期重复出现。取个极端值讨论一下:若next[len]=0,字符串没有任何相同的前后缀,此时T=len, 即周期就是字符串的长度,等同于没有周期性嘛。 若next[len]=len-1,字符串的最大前缀与最大后缀相同,此时T=1,即周期是1,此时字符串所有的字符都相同,像 aaaaa酱紫。代码就略了,因为只要判断len是否能被len-next[len]整除就行啦,若能整除就说明该字符串刚好是一个循环串。
这个问题可以拓展一下:给出一个字符串,你需要添加多少个字符才能让他构成循环串呢?答案是返回T-(len%T)即可。
以上内容都是自己总结的,可能会有谬误之处,欢迎一起讨论~