寻找字符串S中字符串T出现的位置或者次数的问题属于字符串匹配问题。
BF算法:
eg:
主串:s="ababcabcacbab";
模式串:t="abc";
1.变量i,j(初始值为0、1都行)分别指向S、T的第一个位置(这里是指i=1;j=1(i=0;j=0))。
2.对于每一个字母依次进行比较(i<=s.size&&<=t.size(i<s.length&&<t.length)):
<1>,如果对应位置相等,将i,j后移,继续比较后面的字符。
<2>,i,j后退重新匹配,从主串的下一个字符(i-j+2)重新与模式串的第一个字符(j=1)比较。
3.如果j>t.length,说明匹配成功。返回i-t.length
算法模板:
int i=1;j=1;
int s=strlen(a);
int t=strlen(b);
while(i<=s&&j<=t){
if(a[i]==b[i]){//相等:i,j后移,继续比较后续字符
i++;j++;
}else{//不相等
i=i-j+2;//i指向主串的下一个字符
j=1;//j从模式串的第一个字符开始比较
}
if(j>t)
return i-t;
return -1;//返回-1表示未找到
}
AC代码:
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<stdlib.h>
#include<string>
#include<map>
#include<algorithm>
using namespace std;
const int Max_n=1001;
char a[Max_n],b[Max_n];
int main(){
while(~scanf("%s%s",a,b)){
int sum=0;
if(a[0]=='#')
break;
int s=strlen(a);
int t=strlen(b);
int i=0,j=0;//这里注意,我是从下标为0开始的
while(i<s&&j<t){
if(a[i]==b[j]){
i++;
j++;
}
else{
i=i-j+1;//注意下标为0开始和下标为1开始的区别
j=0;
}
if(j>=t){//如果匹配成功
j=0;//j再次从0开始,开始一轮新的匹配,
//i此时就是成功后最后一个字符的下一个字符
sum++;//出现次数+1;
if(i>=s)//i超过主串的长度就跳出
break;
}
}
printf("%d\n",sum);
}
return 0;
}
KMP算法:
next[]数组含义:
<1>.当主串中的第i个字符与模式串中的第j个字符"失配"(不相等)时,主串中第i个字符(此时i指向的位置不动)
应该与模式串中的哪个字符在比较。
<2>.模式串下标x之前的最长等值前后缀的长度(下标从0开始)
模式串下标x之前的最长等值前后缀的长度+1(下标从1开始)
next[]计算值证明略。
kmp、next[]---模板
//下标从1开始(从0开始)
void f(int s){
net[1]=0;//1号位置为0 (net[0]=-1)
for(int i=1,j=0;i<s;){ //(for(int i=0,j=-1;i<s;))
if(j==0||b[i]==b[j]){//j==0,或者此时匹配 //(if(j==-1||b[i]==b[j]))
i++;
j++;
net[i]=j;//i位置就是下标i之前的最长等值前后缀的长度+1
}else{
j=net[j];//如果不匹配,j就是i之前长等值前后缀的最后一个字符,
也就是下标i之前的最长等值前后缀的长度。
}
}
}
int kmp(int s,int t){
int i=1,j=1;
while(i<=s&&j<=t){
if(j==0||a[i]==b[j]){//j=0或者此时匹配,继续下一个位置 (j==-1||a[i]==b[j])
i++;
j++;
}else{
j=net[j];//否则,主串中i位置的字符应该与net[j]比较
}
}
if(j>t)//匹配成功
return i-t;
return -1;
}
nextval数组含义:.当主串中的第i个字符与模式串中的第j个字符"失配"(不相等)时,主串中第i个字符
(此时i指向的位置不动)应该与模式串中的哪个字符在比较。计算原理略。
模板:
void f(int t){
nextval[1]=0;//1号位置为0
for(int i=1,j=0;i<t;){
if(j==0||b[i]==b[j]){//如果匹配
i++;
j++;
if(b[i]!=b[j]){
//net[i]那个位置(也就是j)与当前位置不等
nextval[i]=j;
//nextval[i]的值就是net[j]位置(也就是j)上面的值
}else{//相等
nextval[i]=nextval[j];
//nextval[i]此时的值是j(net[j])
//位置上面的值也就是nextval[j]
}else{
j=nextval[j];
}
}
}
AC代码:
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<map>
#include<algorithm>
#define inf 0x3f3f3f3f
using namespace std;
const int Max_a=1000010;
const int Max_b=10010;
int a[Max_a],b[Max_b],nextval[Max_b];
void f(int t){
nextval[1]=0;
for(int i=1,j=0;i<t;){
if(j==0||b[i]==b[j]){
i++;
j++;
if(b[i]!=b[j]){
nextval[i]=j;
}else{
nextval[i]=nextval[j];
}
}else{
j=nextval[j];
}
}
}
//void f(int t){
// net[1]=0;
// for(int i=1,j=0;i<t;){
// if(j==0||b[i]==b[j]){
// i++;
// j++;
// net[i]=j;
// }else{
// j=net[j];
// }
// }
//}
int kmp(int s,int t){
int i=1,j=1;
while(i<=s&&j<=t){
if(j==0||a[i]==b[j]){
i++;
j++;
}else{
j=nextval[j];
}
}
if(j>t)
return i-t;
return -1;
}
int main(){
int t;
scanf("%d",&t);
while(t--){
int s,t;
scanf("%d%d",&s,&t);
for(int i=1;i<=s;i++)
scanf("%d",&a[i]);
for(int i=1;i<=t;i++)
scanf("%d",&b[i]);
f(t);
printf("%d\n",kmp(s,t));
}
return 0;
}
AC代码:
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<map>
#include<algorithm>
#define inf 0x3f3f3f3f
using namespace std;
const int Max_a=1000010;
const int Max_b=10010;
char a[Max_a],b[Max_b];
int net[Max_b];
//void f(int t){
// nextval[1]=0;
// for(int i=1,j=0;i<t;){
// if(j==0||b[i]==b[j]){
// i++;
// j++;
// if(b[i]!=b[j]){
// nextval[i]=j;
// }else{
// nextval[i]=nextval[j];
// }
// }else{
// j=nextval[j];
// }
// }
//}
void f(int t){
net[1]=0;
for(int i=1,j=0;i<=t;){
if(j==0||b[i]==b[j]){
i++;
j++;
net[i]=j;
}else{
j=net[j];
}
}
}
int kmp(int s,int t){//此题为统计字符串出现的次数
int i=1,j=1,sum=0;
while(i<=s&&j<=t){
if(j==0||a[i]==b[j]){
i++;
j++;
}else{
j=net[j];
}
if(j>t){//如果匹配成功
j=net[j];//和下一次应该比较的字符继续比较
sum++;//出现次数+1
}
}
return sum;
}
int main(){
int n;
scanf("%d",&n);
while(n--){
scanf("%s%s",b+1,a+1);
a[0]=b[0]='0';
int s=strlen(a);
int t=strlen(b);
f(t-1);
printf("%d\n",kmp(s-1,t-1));
}
return 0;
}
AC代码:
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<map>
#include<algorithm>
#define inf 0x3f3f3f3f
using namespace std;
const int Max_a=1005;
const int Max_b=1005;
char a[Max_a],b[Max_b];
int net[Max_b];
//void f(int t){
// nextval[1]=0;
// for(int i=1,j=0;i<t;){
// if(j==0||b[i]==b[j]){
// i++;
// j++;
// if(b[i]!=b[j]){
// nextval[i]=j;
// }else{
// nextval[i]=nextval[j];
// }
// }else{
// j=nextval[j];
// }
// }
//}
void f(int t){
net[1]=0;
for(int i=1,j=0;i<=t;){
if(j==0||b[i]==b[j]){
i++;
j++;
net[i]=j;
}else{
j=net[j];
}
}
}
int kmp(int s,int t){//与上题不同的是,一次匹配结束后,模式串从头开始再次匹配
int i=1,j=1,sum=0;
while(i<=s&&j<=t){
if(j==0||a[i]==b[j]){
i++;
j++;
}else{
j=net[j];
}
if(j>t){
j=1;//由于上述原因,此处j从头开始
sum++;
}
}
return sum;
}
int main(){
while(~scanf("%s%s",a+1,b+1)&&a[1]!='#'){
a[0]=b[0]='0';
int s=strlen(a);
int t=strlen(b);
f(t-1);
printf("%d\n",kmp(s-1,t-1));
}
return 0;
}
此题题意是说一个字符串里如果需要把它补成>=2个相同的字符串需要多少个字符。
aaa 重复的a出现了>=2次,不需要添加字符 0
abca 须补成abcabc 2
abcde 须补成abcdeabcde 2
我们只需要求出重复出现的字符串的长度即可(最小循环节)
AC代码:
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<map>
#include<algorithm>
#define inf 0x3f3f3f3f
using namespace std;
const int Max_a=100005;
char a[Max_a];
int net[Max_a];
void f(int t){
net[0]=-1;
for(int i=0,j=-1;i<t;){
if(j==-1||a[i]==a[j]){
i++;
j++;
net[i]=j;
}else{
j=net[j];
}
}
}
int main(){
int n;
scanf("%d",&n);
while(n--){
memset(net,0,sizeof(net));
scanf("%s",a);
int t=strlen(a);
f(t);
int l=t-net[t];//最小循环节的长度
if(t%l==0){//字符长度刚好是最小循环节的整数倍
if(l==t)//最小循环节的长度=字符串的长度 ab
printf("%d\n",l);
else//一个字符,或者最小循环节出现了整数个(>=2)
printf("0\n");
}else{//不是整数倍
printf("%d\n",l-t%l);//最小循环节的长度-不是最小循环节那部分多出来字符的个数
}
}
return 0;
}
题意:给你一个字符串,某个位置及其之前的字符串如果刚好是最小循环节的整数倍(>1),就输出此时的位置及最小循环节的倍数。
AC代码:
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<map>
#include<algorithm>
#define inf 0x3f3f3f3f
using namespace std;
const int Max_a=1000005;
char a[Max_a];
int net[Max_a];
void f(int t){
net[0]=-1;
for(int i=0,j=-1;i<t;){
if(j==-1||a[i]==a[j]){
i++;
j++;
net[i]=j;
}else{
j=net[j];
}
}
}
int main(){
int n,m=0;
while(~scanf("%d",&n)&&n){
memset(net,0,sizeof(net));
scanf("%s",a);
f(n);
printf("Test case #%d\n",++m);
for(int i=2;i<=n;i++){//根据题意循环直接从2开始
int l=i-net[i];//求出当前前缀的最小循环节
if(i%l==0&&(i/l>1))//如果是当前长度的整数倍&&(>1)j即可输出
printf("%d %d\n",i,i/l);
}
printf("\n");//控制两个示例之间的空格
}
return 0;
}
POJ2752----Seek the Name, Seek the Fame
题意:给你一个字符串,找出一个子串,使得这个字串既是这个字符串的前缀,也是这个字符串的后缀。
首先是一个整个字符串区间,他的最长前后缀找到以后肯定是满足条件的一个字符串,我们在用next数组,将j滑到最长前缀的最后一个字符的位置,我们一直重复这样的过程,直到到第一个位置为止。
AC代码:
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<map>
#include<algorithm>
#define inf 0x3f3f3f3f
using namespace std;
const int Max_a=400005;
char a[Max_a];
int net[Max_a],b[Max_a];
void f(int t){
net[0]=-1;
for(int i=0,j=-1;i<t;){
if(j==-1||a[i]==a[j]){
i++;
j++;
net[i]=j;
}else{
j=net[j];
}
}
}
int main(){
while(~scanf("%s",a)){
memset(net,0,sizeof(net));
memset(b,0,sizeof(b));
int n=strlen(a);
f(n);
int j=n;
int i=1;
while(j!=0){//第一个位置是循环出口
b[i++]=j;//用数组来存放要输出的位置
j=net[j];//j需要一直滑动
}
for(int j=i-1;j>1;j--)
printf("%d ",b[j]);
printf("%d\n",b[1]);
}
return 0;
}
hdu2594----Simpsons’ Hidden Talents
题意:找第一个字符串的前缀和第二个字符串的后缀一样的最长长度
AC代码:
解法一:我们把两个字符串拼接求其最长前后缀的长度,但是这里可能会超过第一个字符串的长度,或者大于第二个字符的长度,所以需要递归,直到长度<=第一个字符串的长度(第二个字符串的长度)。
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<map>
#include<algorithm>
#define inf 0x3f3f3f3f
using namespace std;
const int Max_a=100005;
char a[Max_a],b[Max_a];//这里注意数组长度,因为要拼接,刚开始就是因为这个程序一直超时
int net[Max_a];
void f(int t){
net[0]=-1;
for(int i=0,j=-1;i<t;){
if(j==-1||a[i]==a[j]){
i++;
j++;
net[i]=j;
}else{
j=net[j];
}
}
}
int f(int j,int mmin){
if(j>mmin)//如果当前所求长度大于字符串最小长度
return f(net[j],mmin);//j滑向下一个位置再次重复上面的过程
return j;//最后返回满足题意的长度
}
int main(){
while(~scanf("%s%s",a,b)){
memset(net,0,sizeof(net));
int a1=strlen(a);
int b1=strlen(b);
for(int i=a1,j=0;j<b1;i++,j++)
a[i]=b[j];//拼接字符串
int n=a1+b1;
f(n);
int j=net[n];//前后缀匹配最长的字符串长度
int mmin=min(a1,b1);//两个字符串长度的最小值
int mmax=f(j,mmin);
if(mmax!=0){//如果能找到输出字符和长度
a[mmax]='\0';//这里只是为了方便输出
printf("%s %d\n",a,mmax);
}
else//否则输出0
printf("0\n");
}
return 0;
}
解法二:按照kmp来做
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<map>
#include<algorithm>
#define inf 0x3f3f3f3f
using namespace std;
const int Max_a=50005;
char a[Max_a],b[Max_a];
int net[Max_a];
void f(int t){
net[0]=-1;
for(int i=0,j=-1;i<t;){
if(j==-1||a[i]==a[j]){
i++;
j++;
net[i]=j;
}else{
j=net[j];
}
}
}
int kmp(int s,int t){
int i=0,j=0;
while(i<s&&j<t){
if(j==-1||b[i]==a[j]){
i++;j++;
}else{
j=net[j];
}
if(j>=t&&i!=s)//当匹配成功的时候,如果此时的i没有到最后一个字符的下一个位置
j=net[j];//主串继续与模式串的net[j]位置上面的字符继续比较
}
//最后返回i匹配到最后j的位置(即能够匹配的最大长度)
return j;
}
int main(){
while(~scanf("%s%s",a,b)){//a为模式串,b为主串
memset(net,0,sizeof(net));//此处必须初始化net数组
int a1=strlen(a);
int b1=strlen(b);
f(a1);
int mmax=kmp(b1,a1);//让两个字符串进行模式匹配
a[mmax]='\0';//这里为了方便输出
if(mmax==0)//未匹配成功
printf("0\n");
else
printf("%s %d\n",a,mmax);
}
return 0;
}