26、字符串匹配 KMP 算法

内容来自刘宇波老师算法与数据结构体系课

1、KMP 算法的基本原理

border:已匹配的部分,即是前缀同时也是后缀的子串(非自身、非空),关注最长的 border

image

image

2、KMP 算法正确性的简单证明

image

image

3、什么是 LPS 数组

image

4、LSP 数组的计算

image

image

LPS[i] = length(t[0 ... i] max border)

a = LPS[i - 1],a 是前面的字符串最长的 border
if (t[i] != t[a]),我们可以进一步去看次长的 border、去看次次长的 border,依此类推

if (t[i] != t[a]) 即 红色 != 橙色,那么前面字符串最长的 border(黄色) 就匹配失败了
那么我们退而求其次,看前面字符串次长的 border,我们假设它是绿色,去比较红色和头部绿色的后一个字符(橙色),看它们是否相等
如果匹配成功了,那它就是 t[0 ... i] 最长的 border

头部绿色的后一个字符(橙色),它的下标是多少呢?答案是 LPS[a - 1]
1、绿色是前面字符串次长的 border,那么前面绿色和后面绿色就是相等的
2、后面黄色的后缀是绿色的,前面黄色的前缀也是绿色的,同时前面黄色和后面黄色是相等的
3、绿色是黄色字符串的前缀同时也是后缀,那么绿色就是黄色的一个 border
4、因为绿色是前面整个字符串次长的 border,它肯定比黄色短,同时绿色又是黄色的一个 border,那么绿色肯定是黄色字符串最长的 border
5、黄色字符串的索引是 [0 ... a - 1],那么黄色字符串最长的 border 的长度就是 LPS[a - 1],即绿色的长度为 LPS[a - 1]

image

image

image

5、实现 LPS 数组

1392 - 最长快乐前缀

public class LongestPrefix {

    public String longestPrefix(String s) {
        int[] lps = getLPS(s);

        int len = lps[s.length() - 1];
        return s.substring(0, len);
    }

    private int[] getLPS(String t) {
        // border: 即是前缀又是后缀的子串(非自身、非空)
        // lps[i] 代表 t[0 ... i] 最长的 border 的长度
        int[] lps = new int[t.length()];

        lps[0] = 0;
        for (int i = 1; i < lps.length; i++) {
            int a = lps[i - 1]; // a 代表 t[0 ... i - 1] 最长的 border 的长度

            while (a > 0 && t.charAt(a) != t.charAt(i)) {
                a = lps[a - 1]; // a 代表 t[0 ... i - 1] 次长的 border 的长度
            }

            if (t.charAt(a) == t.charAt(i)) lps[i] = a + 1;
        }

        return lps;
    }
}

6、KMP 算法的实现

/**
 * KMP 算法
 */
public class KMP {

    private KMP() {
    }

    public static int kmp(String s, String t) {
        if (t.length() == 0) return 0;
        if (s.length() < t.length()) return -1;

        int[] lps = getLPS(t);

        int si = 0; // sIndex
        int ti = 0; // tIndex
        while (si < s.length()) {
            if (s.charAt(si) == t.charAt(ti)) {
                si++;
                ti++;
                if (ti == t.length()) return si - t.length();
            }
            else if (ti > 0) ti = lps[ti - 1];
            else si++;
        }

        return -1;
    }

    private static int[] getLPS(String t) {
        // lps[i] = length(t[0 ... i] max border)
        int[] lps = new int[t.length()];

        for (int i = 1; i < lps.length; i++) {
            int a = lps[i - 1];
            while (a > 0 && t.charAt(a) != t.charAt(i)) a = lps[a - 1];
            if (t.charAt(a) == t.charAt(i)) lps[i] = a + 1;
        }

        return lps;
    }
}

7、复杂度分析

image

image

image

image

image

8、模式匹配总结

动态规划的思想
KMP 算法中 LPS 数组的计算:从 LPS[i - 1],推导出 LPS

image

posted @ 2023-04-18 13:55  lidongdongdong~  阅读(47)  评论(0编辑  收藏  举报