java面试题算法篇KMP算法及应用

算法背景

给定两个字符串,判断是否一个字符串包含另外一个字符串,如果包含,返回起始位置。比如:

String  str1=“abceacmk32acmzq”

String str2=“acm”

可以看出,str1包含两处str2,下面红色地方:

abceacmk32acmzq

返回4和10.

常见思路1

遍历str1,先匹配第一个,如果不相同,跳过,继续寻找,如果相同,截取和str2相同长度的子串,比较是否相同,如果相同,则返回,如果不同继续寻找,一直到str1到最后一个字符。代码如下

package com.puhui.goosecard.web;

class GFG {
    public static void main(String[] args) {
        String str1 = "abceacmk32acmzq";
        char[] chars1 = str1.toCharArray();
        String str2 = "acm";
        char[] chars2 = str2.toCharArray();
        for (int i = 0; i < chars1.length; i++) {
            if (chars1[i] != chars2[0]) {
                continue;
            }
            String temp = str1.substring(i, i + str2.length());
            if (temp.equalsIgnoreCase(str2)) {
                System.out.print(i);
            }
        }
    }
}

输出结果:

4
10

时间复杂度:

常见思路2

思路一利用了jdk的接口,如果不用呢?思路二:

  • 设置两个指针i和j从头开始遍历两个字符串
  • 当两个指针都小于字符串长度时,循环后移,比较当前元素,如果相同,继续后移,如果不相等,j重置为0,i从下一个元素开始,如果第一个是从0开始,那么下一个就是1,在程序中是i-j+1,开始,一定注意条件,如果不相同的情况。
  • 直到j等于查询字符长度,输入当前位置,为i-char2.length+1
  • 完成上面逻辑之后,无法匹配下一个重复的子串,所以,如果满足条件3之后,重置j=0;
package com.puhui.goosecard.web;

class GFG {
    public static void main(String[] args) {
        String str1 = "abceacmk32acmzq";
        char[] chars1 = str1.toCharArray();
        String str2 = "acm";
        char[] chars2 = str2.toCharArray();
        /**从头的指针这么写*/
        int i = 0;
        int j = 0;
        while (i < chars1.length && j < chars2.length) {
            if (chars1[i] == chars2[j]) {
                i++;
                j++;
            } else {
                i = i - j + 1;
                j = 0;
            }
            if (j == chars2.length - 1) {
                //多个重复,重新开始寻找,j重置,i不变
                j = 0;
                System.out.println(i - chars2.length + 1);
            }

        }
    }
}

KMP

建立部分匹配表

思路二已经解决了我们的问题,但是是否可以优化呢?当然可以,我们发现如果不匹配,是否可以有效的移动j,而不是回溯到0?

所以,整个KMP的重点就在于当某一个字符与主串不匹配时,我们应该知道j指针要移动到哪利用先前比较的结果跳过一些比较

先看一下思路,先建立一个部分匹配table

m[i]的值就是,小于i的最长后缀。

比如 p1元素是a,最长后缀是a,i=1,所以m[i]的值为0.

比如p4为abab,最长后缀为ab且满足2小于i(4)

比如p6为ababab,最长后缀且满足小于6的是abab,也就是4

比如p9为ababababc,最长后缀且满足小于9的是0,因小于等于8的里面没有c

we look at all the prefixes of P that are suffixes of P1 . . . Pi−1, and find the longest one whose next letter matches Pi

求出P0···Pi的最大相同前后缀长度k

举例

T=ABC ABCDAB ABCDABCDABCD

P=ABCDABD

下面是我的计算过程

有个这个映射关系,怎么用,推到过程比较复杂,详细看下面的连接,

https://web.stanford.edu/class/cs97si/10-string-algorithms.pdf

结果就是

p的移动位数=k-m[k],其中k为当前匹配的个数,m[k]为我们上面推到的结果,也就是(0,0,0,0,1,2,0)

优化原算法

关键点:如果发现不匹配,只能不用回溯到下一个位置,利用已经匹配到元素判断移动位置:

移动位数 = 已匹配的字符数 - 最后一个匹配字符对应的部分匹配值

http://jakeboxer.com/blog/2009/12/13/the-knuth-morris-pratt-algorithm-in-my-own-words/

代码实现

猜你喜欢

转载自blog.csdn.net/hanruikai/article/details/82803096