题目描述
给定一个字符串 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
时间复杂度
构建ne数组的时间复杂度:
在构建ne数组的循环中,i从2到n,其中n是模式串的长度,因此这部分的时间复杂度为O(n)。
KMP匹配的时间复杂度:
在KMP匹配的循环中,i从1到m,其中m是文本串的长度。
内部while循环执行次数取决于j的值,j最大不会超过n(模式串的长度),因此内部while循环的总体执行次数是O(m)。
因此,KMP匹配的时间复杂度也是O(m)。
综合来看,整个程序的时间复杂度是O(n+m),其中n是模式串的长度,m是文本串的长度。这是KMP算法的优点之一,它具有线性的时间复杂度,相对于朴素的字符串匹配算法具有更高的效率,特别是在大文本串中查找多个匹配的情况下。
import java.io.*;
public class Main {
static int n, m;
static int N = 1000010;
static int[] ne = new int[N];
static char[] p;
static char[] s;
public static void main(String[] args) throws Exception{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
n = Integer.parseInt(br.readLine());
String pT = br.readLine();
char[] p = new char[N];
for (int i = 1; i <= n; i++) {
p[i] = pT.charAt(i - 1);
}
m = Integer.parseInt(br.readLine());
String sT = br.readLine();
char[] s = new char[N];
for (int i = 1; i <= m; i++) {
s[i] = sT.charAt(i - 1);
}
//计算ne数组
//ne数组是计算前面的字符和后面的字符相等的个数
//例如:abababab
//在ne[3]的时候,第一个字母a和第三个也就是ne[3]的最后一个a相等,但ab和ba不相等,所以相ne[3]=1
//在ne[4]的时候,第一个字母a和ne[4]的最后一个字母b不相等,但1~2的ab和3~4的ab相等,所以ne[4]=2
//同理ne[5]=3因为1~3的aba和3~5的aba相等
//i从2开始因为第一个没法比较默认就是0
for (int i = 2, j = 0; i <= n; i++) {
//如果出现一个数与j + 1这个数不相等,就让j等于上一个数的ne
while(j > 0 && p[i] != p[j + 1]) j = ne[j];
//如果相等就让j++
if(p[i] == p[j + 1]) j ++;
ne[i] = j;
}
//KMP实现
//例如:abababab
for (int i = 1, j = 0; i <= m; i++) {
//j还在0的时候如果赋值就可能造成跳过一些数
//如果两个数组同位置比较不相同的话就让j移到j的ne的位置因为ne前部分和后部分相等,且ne后半部分与与大数组上面两相等
//例如abababc
// ababc
while(j > 0 && s[i] != p[j + 1]) j = ne[j];
if(s[i] == p[j + 1]) j ++;
//如果相等的个数等于需要比较的模板数组的长度,说明该输出了
if(j == n) {
bw.write((i - n) + " ");
//输出完后继续向下比较,所以要让j = 上一个数的ne
j = ne[j];
}
}
bw.flush();
bw.close();
br.close();
}
}