1.求解类型
字符串匹配。给你两个字符串,寻找其中一个字符串是否包含另一个字符串。
如果包含,返回包含的起始位置。如下面两个字符串:
char *str = "bacbababadababacambabacaddababacasdsd";
char *ptr = "ababaca";
str有两处包含ptr。分别在str的下标10,26处。
“bacbababadababacambabacaddababacasdsd”;
2.算法说明
我们从原始字符串str(假设长度为n)的第一个下标、
选取和ptr长度(长度为m)一样的子字符串进行比较。
如果一样,就返回开始处的下标值,不一样,选取str下一个下标。
继续选取长度为m的字符串比较,直到str的末尾(即下标移动到n-m)。
这样的时间复杂度是O(n*m)。
KMP算法:可以实现复杂度为O(m+n)。
充分利用了目标字符串ptr的性质(比如里面部分字符串的重复性,
即使不存在重复字段,在比较时,实现最大的移动量)。
(1)原(短)字符串自我匹配
考察目标字符串ptr:ababaca
这里我们要计算一个长度为m的转移函数next。
next数组的含义就是一个固定字符串的 最长前缀 和 最长后缀 相同的长度。
比如:abcjkdabc,那么这个数组的最长前缀和最长后缀相同必然是abc。
cbcbc,最长前缀和最长后缀相同是cbc。
abcbc,最长前缀和最长后缀相同是不存在的。
预处理的数组next:next [ i ] 表示 “str中以i结尾的非前缀字串(上文说的后缀)”
与 “str的前缀” 能够匹配到的最长长度。
// A="ababacb"; 长度为m
// B="abababaababacb"; 长度为n
(注意:以下代码的字符串输入都从1开始)
//next[i]
next[1]=0; j=0;
for(int i=1;i<m;i++){ //a数组自我匹配,从i+1=2与1比较开始
while(j>0&&a[i+1]!=a[j+1]) j=next[j];
//↑自身无法继续匹配且j还没减到0,考虑返回匹配的剩余状态
if(a[i+1]==a[j+1]) j++; //这一位匹配成功
next[i+1]=j; //记录这一位向前的最长匹配
}
(2)str,ptr的匹配
数组f:f [ i ] 表示 “ ptr中以 i 结尾的子串 ” 与 “ str的前缀 ” 的最长匹配长度。
在b串中寻找a串出现的位置:
j=0;
for(int i=0;i<n;i++){ //扫描b,寻找a的匹配
while(b[i+1]!=a[j+1]&&j>0) j=next[j];
//↑不能继续匹配且j还没减到0(之前的匹配有剩余状态)
if(b[i+1]==a[j+1]) j++; //匹配加长,j++
if(j==m){ //【一定要把这个判断写在j++的后面!】
printf("%d\n",i+1-m+1); //子串起点在母串b中的位置
j=next[j]; //继续寻找匹配
} //【↑↑巧妙↑↑这里不用返回0,只用返回上一匹配值】
}
求f数组(a与b最大的匹配长度):
//f[i]
j=0;
for(int i=0;i<n;i++){ //扫描b
while(( j==m || b[i+1]!=a[j+1] ) && j>0) j=next[j];
//↑不能继续匹配且j还没减到0(之前的匹配有剩余状态)
//↑↑↑或a在b中找到完全匹配
if(b[i+1]==a[j+1]) j++; //匹配加长,j++
f[i+1]=j; //此位置和先前组成的最长匹配
// (if(f[i+1]==m),此时a在b中找到完全匹配)
}
3.例题
(1)剪花布条(hdu2087)
#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
//【剪花布条】能从花布条中剪出多少小花条? [注意:不能重叠!]
char a[1009],b[1009];
int nextt[1009],n,m;
void pre(){ //预处理:求next[i]
nextt[1]=0; int j=0;
for(int i=1;i<m;i++){ //b数组自我匹配,从1与前一位0比较开始
while(j>0&&b[i+1]!=b[j+1]) j=nextt[j];
//↑自身无法继续匹配且j还没减到0,考虑返回匹配的剩余状态
if(b[i+1]==b[j+1]) j++; //这一位匹配成功
nextt[i+1]=j; //记录这一位向前的最长匹配
}
}
int kmps(){ //在a串中寻找b串的出现次数
int ans=0,j=0;
for(int i=0;i<n;i++){ //扫描a
while(b[j+1]!=a[i+1]&&j>0) j=nextt[j];
//↑不能继续匹配且j还没减到0(之前的匹配有剩余状态)
if(j==m){ ans++; j=0; } //j=0,保证不重叠
if(b[j+1]==a[i+1]) j++; //匹配加长,j++
}
return ans;
}
int main(){
while(cin>>a+1){ //从1开始读入字符串a
if(a[1]=='#') break; //输入结束
scanf("%s",b+1);
m=strlen(b+1),n=strlen(a+1);
pre(); printf("%d\n",kmps());
}
return 0;
}
(2)字符串周期(poj 2406)
#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
/*【power strings】poj2406
给出一个不超过1e6的字符串,求这个字符串最多有多少个周期。 */
char a[1000005];
int nextt[1000005],n;
void pre(){ //nextt[i]
nextt[1]=0; int j=0;
for(int i=1;i<n;i++){ //a数组自我匹配,从i+1=2与1比较开始
while(j>0&&a[i+1]!=a[j+1]) j=nextt[j];
//↑自身无法继续匹配且j还没减到0,考虑返回匹配的剩余状态
if(a[i+1]==a[j+1]) j++; //这一位匹配成功
nextt[i+1]=j; //记录这一位向前的最长匹配
}
}
int main(){
while(1){
scanf("%s",a+1);
if(a[1]=='.') break;
n=strlen(a+1);
pre();
if(n%(n-nextt[n])==0) //nextt的定义
//nextt[i]表示 “str中以i结尾的非前缀字串(某段后缀)”
//与 “str的前缀” 能够匹配到的最长长度。
printf("%d\n",n/(n-nextt[n]));
else printf("1\n");
}
return 0;
}
(3)最小循环元长度和最大循环次数(poj1961)
#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
/*【period】poj 1961
给你一个字符串,求这个字符串到第i个字符为止的最小循环元长度和最大循环次数。 */
char a[1000005];
int nextt[1000005],n,T;
void pre(){ //nextt[i]
nextt[1]=0; int j=0;
for(int i=1;i<n;i++){ //a数组自我匹配,从i+1=2与1比较开始
while(j>0&&a[i+1]!=a[j+1]) j=nextt[j];
//↑自身无法继续匹配且j还没减到0,考虑返回匹配的剩余状态
if(a[i+1]==a[j+1]) j++; //这一位匹配成功
nextt[i+1]=j; //记录这一位向前的最长匹配
}
}
int main(){
while(cin>>n&&n){
scanf("%s",a+1); pre();
printf("Test case #%d\n",++T); //T从1开始
for(int i=2;i<=n;i++){
if(i%(i-nextt[i])==0&&i/(i-nextt[i])>1)
printf("%d %d\n",i,i/(i-nextt[i]));
}
printf("\n");
}
return 0;
}
(4)bzoj 1355/1511/3620/3942
【等待填坑中Σ( ° △ °|||)︴】