刷题日记08 | 28. 实现 strStr()、 459.重复的子字符串

76 阅读2分钟

刷题日记08

今天的主题是KMP! KMP的核心就是算出前缀表。

什么是前缀表?

前缀表中的值是字符串的前缀集合与后缀集合的交集中最长元素的长度

例如,对于”aba”,它的前缀集合为{”a”, ”ab”},后缀 集合为{”ba”, ”a”}。两个集合的交集为{”a”},那么长度最长的元素就是字符串”a”了,长 度为1,所以对于”aba”而言,它在前缀表中对应的值就是1。再比如,对于字符串”ababa”,它的前缀集合为{”a”, ”ab”, ”aba”, ”abab”},它的后缀集合为{”baba”, ”aba”, ”ba”, ”a”}, 两个集合的交集为{”a”, ”aba”},其中最长的元素为”aba”,长度为3。

前缀表为什么能加速查找?

当出现字符串不匹配时,可以通过前缀表跳过一部分之前已经匹配的文本内容,避免从头再去做匹配了。

时间复杂度分析

其中n为文本串长度,m为模式串长度,因为在匹配的过程中,根据前缀表不断调整匹配的位置,可以看出匹配的过程是O(n),之前还要单独生成next数组,时间复杂度是O(m)。所以整个KMP算法的时间复杂度是O(n+m)的。

暴力的解法显而易见是O(n × m),所以KMP在字符串匹配中极大地提高了搜索的效率。

如何计算前缀表(next数组)?

见下面例题

28. 找出字符串中第一个匹配项的下标

class Solution {
    public int strStr(String haystack, String pattern) {
        int m = haystack.length();
        int n = pattern.length();
        if(n == 0) return 0;

        int[] next = new int[pattern.length()];
        getNext(pattern, next);

        int i = 0;
        int j = 0;
        while(i < m && j < n){
            if(j == -1 || haystack.charAt(i) == pattern.charAt(j)){
                i++;
                j++;
            }else{
                j = next[j];
            }
        }
        if(j == n){
            return i - j;
        }else{
            System.out.println("i = " + i);
            System.out.println("j = " + j);
            return -1;
        }
    }

    public void getNext(String p, int[] next){
        // 因为next表整体右移一位,所以next[0]取-1,方便编程
        next[0] = -1;
        // i指针为字符串指针,从1开始,因为next[0]已经设为-1
        int i = 0;
        // j指针为pattern串指针,从0开始
        int j = -1;
        while(i < p.length() - 1){
            // j = -1 意味着上次匹配不成功,i指针需要移动,此时j指针同时移动,保证下次j==0
            // 匹配成功则同时移动i,j指针 同时将j的值储存到next表中
            if( j == -1|| p.charAt(i) == p.charAt(j)){ 
                i++;
                j++;
                next[i] = j;
            }else{
            // 匹配不成功则将j设置为共同前缀的后面
                j = next[j];
            }
        }
    }
}

459. 重复的子字符串

class Solution {
    public boolean repeatedSubstringPattern(String s) {
        if(s.length() <= 1) return false;
        int[] next = new int[s.length()];
        String pattern = s.substring(0, getMaxNext(s, next) + 1);
        int j = 0;
        for(int i = 0; i < s.length(); i++){
            if(s.charAt(i) == pattern.charAt(j)){
                j++;
                j = j % pattern.length();
            }else{
                return false;
            }
        }
        return true;
    }
    public int getMaxNext(String s, int[] next){
        next[0] = -1;
        int i = 0;
        int j = -1;
        int res = Integer.MIN_VALUE;
        while(i < s.length() - 1){
            if(j == -1 || s.charAt(i) == s.charAt(j)){
                i++;
                j++;
                next[i] = j;
                res = Math.max(res, j);
            }else{
                j = next[j];
            }
        }
        return res == Integer.MIN_VALUE ? 0 : res;
    }
}