题目
给出两个字符串s1和s2,若s1某个区间的子串与s2完全相同,则称s2在s1中出现了,现在请你求出s2在s1中所有出现的位置。
解题
KMP模板题
首先思考最朴素的做法,就是两层for循环,时间复杂度O(nm)。
但是其实,每次遇到不匹配的字母,我们并不需要在s1上逐个移动,每次移动对s2的所有字符逐个判定,而是可以通过使s1和s2上某个特定的字符(串)对齐,来实现移动。
而这个“特定的字符(串)”是可以通过预处理存在一个数组next中的,这样一来,每次移动的操作就由O(n)降为O(1)。
要使s1和s2匹配,我们的想法是,首先对s1进行遍历,在遍历过程中,判断s1和s2对应位置是否相同,如果不相同,就进行移动,让s1的当前位置与s2的next[j](j是当前s2中失配的位置)位置对齐;如果此时对应位置上的元素相同,则j++,表示又成功匹配了一位。当j==s2长度时,匹配成功。
要怎么实现next数组的预处理呢?我们采用的思想是让s2串自己匹配自己。
也就是说,我们对s2进行遍历,在遍历过程中,如果当前字符失配了,就直接跳到next数组所指的下一个位置。如果成功匹配,则j++,即j向后移一位;再使next[i]=j(j表示匹配到s2的第j位了),即当下次s2中某一位失配时(比如说第i+1位),我们可以直接将第i位跳到s2中的第j位与其进行匹配。换句话说,s2中第i位和第j位的元素是相同的,在s1和s2的匹配的移动过程中,我们可以通过将s1的某一位分别与s2的第i位、第j位对齐来完成一次移动。
#include<iostream>
#include<cstring>
using namespace std;
const int N=1e6+10;
char ts1[N],ts2[N];
int ne[N];
int main()
{
cin>>ts1;
cin>>ts2;
int n=strlen(ts1);
int m=strlen(ts2);
char s1[N],s2[N];
for(int i=1;i<=n;i++) s1[i]=ts1[i-1];
for(int i=1;i<=m;i++) s2[i]=ts2[i-1];
for(int i=2,j=0;i<=m;i++)
{
while(j&&s2[i]!=s2[j+1]) j=ne[j];
if(s2[i]==s2[j+1]) j++;
ne[i]=j;
}
for(int i=1,j=0;i<=n;i++)
{
while(j&&s1[i]!=s2[j+1]) j=ne[j];
if(s1[i]==s2[j+1]) j++;
if(j==m)
{
printf("%d\n",i-m+1);
j=ne[j];
}
}
for(int i=1;i<=m;i++) cout<<ne[i]<<" ";
}