831. KMP字符串 字符串 KMP

给定一个模式串 S,以及一个模板串 P,所有字符串中只包含大小写英文字母以及阿拉伯数字。
模板串 P 在模式串 S 中多次作为子串出现。
求出模板串 P 在模式串 S 中所有出现的位置的起始下标。
输入格式
第一行输入整数 N,表示字符串 P 的长度。
第二行输入字符串 P。
第三行输入整数 M,表示字符串 S 的长度。
第四行输入字符串 S。
输出格式
共一行,输出所有出现位置的起始下标(下标从 0 开始计数),整数之间用空格隔开。
数据范围
1≤N≤105
1≤M≤106
输入样例:

  1. 3
  2. aba
  3. 5
  4. ababa

输出样例:

  1. 0 2

算法思想:

相较于暴力算法,利用已经匹配好的部分子串的信息,不回退模板串,而只回退模式串。
当然回退多少需要next数组来帮助,含义是非平凡(不是原串自身)最长公共前后缀的长度

时间复杂度 O(m + n)
空间复杂度 O(n)

  1. import java.util.*;
  2. import java.io.*;
  3. public class Main {
  4. public static void main(String[] sss) throws IOException{
  5. // Scanner sc = new Scanner(new InputStreamReader(System.in));
  6. BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
  7. BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
  8. int n = Integer.parseInt(br.readLine());
  9. String p = br.readLine();
  10. int m = Integer.parseInt(br.readLine());
  11. String s = br.readLine();
  12. int[] ne = new int[n];
  13. //模式串与模式串匹配求ne。 本质是dp
  14. //定义k-max(str)为字符串str中前缀与后缀相同的最大长度。
  15. // 例如 str = "abcab", k-max(str) = 2
  16. //定义sub(str, n) 为字符串str从0开始, 到n结束的子串,左闭右开[0, n),即子串长度为n
  17. //所以有 k-max(sub(str, n))表示str的从0开始的长度为n的子串的最长相同前后缀。
  18. //ne[i] = k-max(sub(str, i+1))
  19. //也就是说ne是这样一个数组,数组中的每个元素存储了模式串对应位置的子串的最长前后缀。
  20. //ne[0] = k-max(sub(str, 1))
  21. //ne[1] = k-max(sub(str, 2))
  22. //i 指向模板串, j 指向模式串 (相对来说!!)
  23. //而且i和j都指向待比较字符
  24. //模式串在运行过程中就相当于是前缀,而模板串就是后缀、
  25. //注意初始化i=1,不能写成i=0,否则会导致ne[0] = 1,从而导致一连串错误
  26. for (int i = 1, j = 0; i < n; i++) {
  27. //如果当前位置不匹配,j只能回退到能匹配到的最近位置
  28. //while的两个结束条件:
  29. //1. 最坏情况,j == 0, 退到开头(第一个字符), 退无可退。
  30. //2. 当前待比较字符不同
  31. while (j > 0 && p.charAt(i) != p.charAt(j)) j = ne[j - 1];
  32. //这个if语句的含义是:
  33. //判断上面的while因何种原因退出
  34. //1. 因j > 0 不满足而退出说明此时j == 0,用if判断最长前后缀是1还是0
  35. //2. 因去p[i] != q[j]退出说明此时p[i]==p[j],if结果为真
  36. if (p.charAt(i) == p.charAt(j)) j++;
  37. //更新当前子串最长前后缀
  38. ne[i] = j;
  39. // 小小的优化
  40. // 在匹配第i+1个字符时如果不匹配,需要找ne[i]继续匹配
  41. // 如果p[i+1]==p[ne[i]],下一次仍不匹配,需要找ne[ne[i]]继续匹配
  42. if (i + 1 < m && p.charAt(i + 1) == p.charAt(j))
  43. ne[i] = ne[j];
  44. }
  45. //正式进行字符串匹配
  46. for (int i = 0, j = 0; i < m; i++) {
  47. while (j > 0 && s.charAt(i) != p.charAt(j)) j = ne[j - 1];
  48. if (s.charAt(i) == p.charAt(j)) j++;
  49. if (j == n) {
  50. j = ne[j - 1];
  51. bw.write(i - n + 1 + " ");
  52. }
  53. }
  54. br.close();
  55. bw.close();
  56. }
  57. }

更多例题:

459. 重复的子字符串 KMP的应用,找循环节
可以用KMP或者Z函数
结论:若S是周期串,则
最小循环节长度为len <==> s[0, n - 1 - len] = s[len, n - 1]
AcWing. 141. 周期 KMP的应用,459的加强版
只会用KMP,Z函数的解法感觉很麻烦
214. 最短回文串 KMP的应用,找最长回文前缀

参考:
如何更好地理解和掌握 KMP 算法?
y总的参考代码以及下面的评论代码