超简单KMP实现讲解

KMP

最长公共前后缀

在开始了解KMP之前,我们先掌握这个最长公共前后缀的求解方法。
下面我们先肉眼看一下,最长公共前后缀:
例如,对于字符串 abccab:

  • 前缀字符串为:a,ab,abc,abcc,abcca
  • 后缀字符串为:
    b,ab,cab,ccab, bccab
    其中,最长公共前后缀为ab
    另外,我们拆解这个字符串,看看对于它的各个位置上,如果0开始截取到index位,最长公共前后缀长度表:
a(index=0) b(index=1) c(index=2) c(index=3) a(index=4) b(index=5)
0 0 0 0 1 2

观察上面表,index 在[0,3]之间,都不存在公共前后缀;index=4时候,对于abcca,公共前后缀只有a;index=5时候,对于abccab,最长公共前后缀为ab
观察上表的最长公共前缀长度值的变化,会发现最大值 ,是逐渐上升上去的,随着字母的累加到达最大,并且最大值依赖于前面的值。感觉是不是有点儿动态规划那意思。

 /**
     * 获取最长公共前后缀长度
     *
     * @param s
     * @return
     */
    private int[] getNext(String s) {
        int[] result = new int[s.length()];
        int len = 0, i = 1;
        while (i < s.length()) {
            if (s.charAt(i) == s.charAt(len)) {
                result[i++] = ++len;
            } else if (len == 0) {
                //匹配失败,匹配长度为 0,无公共串
                result[i++] = 0;
            } else {
                //匹配失败,看看公共串有没有前缀和后缀相等的部分,有的话,相等部分的后一个字母比较
                  // aafaaa ,到最后一个a时候,匹配失败,len=2,根据前面匹配的公共串,看看公共串的公共串
                len = result[len - 1];
            }
        }
        return result;
    }

比较有意思的代码len = result[len - 1] :
超简单KMP实现讲解_第1张图片

  • 如果M和N为最长公共前后缀。则1,2部分等于3,4部分.且1=3,2=4,
  • 如果1和2部分为M的最长公共前后缀,且1=2,那么刺激的来了,则1=4

所以计算M+N之后的最长公共前后缀长度,初始值可以从result[len - 1]计算,其中len为M+N段最长公共前后缀长度。

前面也说了,最长公共前后缀的长度,其实是基于前面的结果计算的。

KMP

对于13个人,其中肯定是有一个人的生日与另一个人的生日在同一月份。同样,对于字符串来讲,字符也也是会有重复的。KMP就是充分利用字符的前后重复度来匹配字符串的。

逐个匹配时,当中间某个字符不匹配时,例如:source=ababababc中查找target=ababc,index按照机器无脑的匹配规则,我们会回退到target字符串的index=0位置,然后在source串上index=1的位置接着查找。
但是在KMP中,因为我们有被查找串串的最长公共前后缀长度表,利用target字符的表,我们会回退到target字符串的index=2位置,同时,由于source中,index=[2,3]处的ab,并不匹配target的第二对ab,所以source的指针不用回退,在source串上index=4的位置接着往下对比查找。

通过对比回退位置,我们发现,利用我们求的回退表,可以在每次不匹配时候,回退尽量少的位置。

  /**
     * KMP
     *
     * @param haystack
     * @param needle
     * @return
     */
    public int strStr2(String haystack, String needle) {
        if (haystack.length() < needle.length()) {
            return -1;
        }
        if ("".equals(haystack) && "".equals(needle)) {
            return 0;
        }
        if (haystack.length() < 1) {
            return -1;
        }
        if (needle.length() < 1) {
            return 0;
        }
        int[] next = getNext(needle);
        int i = 0;
        int j = 0;
        while (i < haystack.length() && j < needle.length()) {
            if (haystack.charAt(i) == needle.charAt(j)) {
                i++;
                j++;
            } else {
                //回退
                if (j == 0 || i == 0) {
                    //第一个
                    i++;
                    j++;
                }
                j = next[j - 1];
            }
        }
        return j == needle.length() ? i - j : -1;
    }

总结

对于KMP算法,首先要根据被查找字符串,构建最长公共前后缀的表,我们要利用前后的对称性,在遇到不匹配字符时候,去查表,获取回退位置。
但是个人感觉,这个实战起来,很依赖于被查找串的重复性,重复性越大,查找起来越容易。

你可能感兴趣的:(LeetCode练习)