KMP 算法解决的问题:
    字符串 str1 和 str2,str1 是否 包含 str2,如果包含返回 str2 在 str1 中的位置。

    暴力遍历的方法:
    匹配每个 str1 的字符串,在极端情况下复杂度极高,如 str1 = “111111112”,str2=”112”。复杂度几乎为 KMP - 图1(N、M 分别为 str1、str2 的长度)

    前置知识:
    k 为字符串前后缀相同的最大长度。
    如:对于字符串 abbabb, k = 3

    长度 前缀 后缀 是否相同
    1 a b
    2 ab bb
    3 abb abb
    4 abba babb
    5 abbab bbabb

    对于字符串 aaaaa 来说,k = 4

    长度 前缀 后缀 是是相同
    1 a a
    2 aa aa
    3 aaa aaa
    4 aaaa aaaa

    str2 的每个下标对应的 k:k 之前的字符串最大
    image.png
    在传统的遍历过程中,如果 str1 出现在匹配第 i 位置是否和 str2 不同,接着就会再比较 i=i+1 位置
    image.png
    但是,str2 在已经知道了每个下标的 k,就可以直接这样匹配
    image.png
    即,str1 在 i 位置和 str2 做匹配,如果在 str1 的 x 位置str2 的 y 位置 不匹配,那么就可以使 y = k[y] i = x-k[y],然后继续匹配

    能够这样匹配 KMP 算法保证了两个条件:

    1. str2 每个位置上的 k 都保证了该位置下标之前的前后缀相同的最大长度,也就是说:在 k > 0 时, str2[0] ~ str2[k[i]] (abef)和 str2[k[i-k[i]]] ~ str2[k[i-1]] (abef)相同

    image.png

    1. [i, x-k[y]] (abefzq) 范围匹配不到 str2

    如下图 str2 黄色的部分是在 y 位置的前后缀的最大长度。 str2 和 str1 的 i 位置匹配并在 str1 的 x 位置 str2 的 y 位置匹配不上,str2 在 str1 [i, j] 范围内匹配不到
    image.png
    证明过程:
    假设在 [i, j] 范围上存在 m 位置 str2 能匹配到的字符串
    所以,在 str1 上一定存在 [m, x-1] 范围上的字符串一定能和 str2 的 [0, x-m] 相同。如下图的绿色部分
    image.png
    也就是说,str2 上的[m , y-1] 的字符串和 [0, x-m] 相同,如下绿色部分
    image.png
    即,str2 中绿色的部分是相同的,改推论和“str2 最大的前后缀是黄色的部分”的结论相悖,所以在 [i, j] 上不存在和 str2 匹配的字符串。

    1. public class KMP {
    2. static int findIndexOf(String str1, String str2) {
    3. if (str1 == null || str1.length() == 0 || str2 == null || str2.length() == 0 || str1.length() < str2.length()) {
    4. return -1;
    5. }
    6. char[] str1Arr = str1.toCharArray();
    7. char[] str2Arr = str2.toCharArray();
    8. int i1 = 0;
    9. int i2 = 0;
    10. int[] kArr = getKArray(str2Arr);
    11. while (i1 < str1Arr.length && i2 < str2Arr.length) {
    12. if (str1Arr[i1] == str2Arr[i2]) {
    13. i1++;
    14. i2++;
    15. } else if (kArr[i2] == -1) {
    16. i1++;
    17. } else {
    18. i2 = kArr[i2];
    19. }
    20. }
    21. // i1 或者 i2 越界了
    22. return i2 == str2Arr.length ? i1 - i2 : -1;
    23. }
    24. // 获取每个位置的最大前后缀
    25. private static int[] getKArray(char[] chars) {
    26. if (chars.length == 1) return new int[]{-1};
    27. int[] kArr = new int[chars.length];
    28. kArr[0] = -1;
    29. kArr[1] = 0;
    30. // 当前的下标
    31. int i = 2;
    32. // 统计最大的前后缀
    33. int count = 0;
    34. while (i < kArr.length) {
    35. if (chars[i - 1] == chars[count]) {
    36. kArr[i++] = ++count;
    37. } else if (count > 0) {
    38. // 取上一个位置的 k
    39. count = kArr[count];
    40. } else {
    41. kArr[i++] = 0;
    42. }
    43. }
    44. return kArr;
    45. }
    46. public static void main(String[] args) {
    47. String str1 = "abbaefzqabbabbefdqe";
    48. String str2 = "abbabb";
    49. System.out.println(str1);
    50. System.out.println(str2);
    51. System.out.println(findIndexOf(str1, str2));
    52. }
    53. }

    结果

    abbaefzqabbabbefdqe
    abbabb
    8