题目
给定一个模式串S,以及一个模板串P,所有字符串中只包含大小写英文字母以及阿拉伯数字。
模板串P在模式串S中多次作为子串出现。
求出模板串P在模式串S中所有出现的位置的起始下标。
输入格式
第一行输入整数N,表示字符串P的长度。
第二行输入字符串P。
第三行输入整数M,表示字符串S的长度。
第四行输入字符串S。
输出格式
共一行,输出所有出现位置的起始下标(下标从0开始计数),整数之间用空格隔开。
数据范围
1≤N≤105
1≤M≤106
输入样例:
3
aba
5
ababa
输出样例:
0 2
题目分析
给出一个较长的字符串和短些的模板串,目的是在字符串中找到模板串,并返回匹配到的串中的首索引,如果有多个匹配,那么要返回多个数值.
令字符串的索引为i
,模板串的索引为j
朴素算法是i遍历,在每个i中,比较i指针和j指针的值是否相同,相同则j指针前移,i继续遍历(也就是i和j同时前行),不同则i继续遍历,j退回起点.
朴素做法的问题在于,每次j都是退回起点,从而浪费了匹配到的信息,如果能够少退回几格,那么效率就能得到很大提升.
算法分析
- next数组
明确数组的含义: 数组针对的是模板串,数组的索引表示的是模板串的某个位置的索引,数组的值表示的是模板串以某位置(i)为边界,前面的字符串的最长匹配的前后缀的长度.
eg: ababab|
在|前,最长的前后缀长度是4(abab),表示的含义是i前的字符串前面n个字符和后面n个字符完全相同.
Q:那么为什么要做这个工作呢?找到模板串任意位置的最长前后缀有什么意义呢?
A:充分利用之前匹配成功的信息,减少需要对比的次数. - 匹配过程
让i依次遍历,知道遍历到字符串的最后一个,表示字符串所有的字符都考虑完全.
首先要知道j指针的含义:表示j之前所有的字符都匹配了
在每次遍历中,观察i指针与j指针(代码的含义是j指针的下一个)所指的字符是否相同,如果相同,j指针前移,i本次遍历结束(i指针前移),最好的情况是有很多都匹配,这说明i和j都前移了很多字符,也就意味着此刻j前的所有字符都是匹配的
;如果不同,我们让j前移变为next[j],也就是说,我们的期望降低了,我们只认为是前缀匹配了,然后再次进行匹配,如果下一个字符匹配成功,那么继续前移;如果下一个字符匹配失败,说明我们还需要继续降低期望,变成"前缀的前缀"来进行匹配.依次类推.
算法实现
- next数组是如何实现的?
我们给出两个指针i=0,j=2
,j表示的就是边界,也即是我们要看的就是j前的最长前后缀,而i表示的是进行匹配的指针.
模板串下标从1开始
j之所以选择2是因为从2开始才有两个字符进行匹配,才有意义,i选择0是因为我们比较的是i+1和j的值,如果相同了i才进行移动.
现在,我们制定一个规则,只有当i+1和j的值相同时,两指针才进行移动,表示匹配成功;否则,i前移至next[i],继续进行匹配,这样,i一直指向的是匹配后缀的字符串,并且保证能增加时增加(最长),不能增加(不匹配)就退而求其次,继续匹配.
2.整体排序是如何实现的?
令字符串的索引为i
,模板串的索引为j
当i和j匹配时,j++,i到下一个循环(i++);
当i个j不匹配时,j退到next[j],继续进行匹配(while语句)
当j到达模板串的末尾,表示全部匹配,此时进行要求操作(输出,如果要匹配所有则强制让j进行回退到next[j]即可)
源代码
import java.io.*;
class Main{
public static void main(String[] args)throws Exception{
BufferedReader br=new BufferedReader(new