题目大意:给出两个字符串s和p,其中p为s的子串,求出p在s中所有出现的位置。
p[0, i-1]中,若存在最长的相等的后缀与前缀,则next[i]为那个前缀的末尾位置的下一个位置。匹配时,如果p[i]!=s[j],因为p[0,i-1]中的后缀已经匹配成功,则把与其相等的前缀放在那个后缀的位置,也会匹配成功。所以让i=next[i]继续匹配即可。
求next数组,要用递推。每次while循环体内解决的问题是:已知next[j]==k,求next[j+1]。如果p[j]==p[k],对于j+1来说,前缀的长度和后缀的长度都加1,还相等,把next[j+1]设成k+1即可;否则,令k=next[k],根据定义,p[0,next[k]-1]该前缀1能在p[0,k-1]中找到与其相等的后缀2,该p[0,k-1]整体该前缀3会在p[0,j-1]中找到与其相等的后缀4。因为1=2,2是3后缀,3=4,故1与4的后缀相等。就这样不断循环即可。
#include <cstdio> #include <cstring> #include <cassert> using namespace std; const int MAX_STR = 1000010; void SetNext(char *p, int *next) { int len = strlen(p); int j = 0, k = -1; next[0] = -1; while (j < len) { if (k == -1 || p[j] == p[k]) { /*if (p[j + 1] == p[k + 1]) next[++j] = next[++k]; else*/ next[++j] = ++k; } else k = next[k]; } } void GetP(char *s, char *p, int *next, int *ans) { int sLen = strlen(s), pLen = strlen(p), sPos = 0, pPos = 0, ansCnt = 0; while (sPos < sLen) { if (pPos == -1 || s[sPos] == p[pPos]) { sPos++; pPos++; } else pPos = next[pPos]; if (pPos == pLen) { ans[ansCnt++] = sPos - pPos + 1; pPos = 0; sPos = sPos - pLen + 2; } } } int main() { #ifdef _DEBUG freopen("c:\noi\source\input.txt", "r", stdin); #endif static char s[MAX_STR], p[MAX_STR]; static int next[MAX_STR], ans[MAX_STR], pLen; scanf("%s%s", s, p); pLen = strlen(p); SetNext(p, next); GetP(s, p, next, ans); for (int i = 0; ans[i]; i++) printf("%d ", ans[i]); for (int i = 1; i < pLen + 1; i++) printf("%d ", next[i]); printf(" "); return 0; }
注意:
- 是k==-1(pPos==-1),而不是next[k]==-1(next[pPos]==-1)。
- 设置next时,k初值为-1,而不是0(看next的定义)。
优化:若p[next[j]]==p[j],则实际匹配时,我们还要求next[next[j]]。因此,把代码中注释部分加上即可。