【动态规划】KMP算法

 28. 实现 strStr()

参考来源

作者:labuladong
链接:https://leetcode-cn.com/problems/implement-strstr/solution/kmp-suan-fa-xiang-jie-by-labuladong/

 

 

 

 

 动态规划版的kmp算法比较好理解。同样是基于有限的状态来更新和推进的。其中最重要的思想就是文中定义的影子状态X。

影子可以理解为当前状态的上一个状态。上一个状态遇到pat[j-1]会变成j,当前状态只有遇到pat[j]才能继续推进状态。所以如果遇到除了pat[j]以外的字符,就应该进行状态回退。那么应该回退到什么状态呢?直接回退到0的话就是暴力算法。注意到上一个状态X就已经遇到过pat.charAt[j]了,所以就回退到上一个状态X遇到pat.[j]时所回退到的状态就行了。

 关于X的更新,按照search的思路来理解可能会比较好记,是为了维护尽可能长的相同前缀。可以理解为快慢指针。j在前面匹配pat,X追着j在后面匹配pat,为的就是当j不匹配时可以尽可能少地回退。

例如ABABC,走到第二个A时,如果下一个字符不是B,那么应该回退到第一个A而不是开头;如果下一个字符是B,那么回退就应该回退到第一个B。

class Solution {
    private int[][] dp;
    private String pat;

    public int strStr(String haystack, String needle) {
        if(haystack.length()==0&&needle.length()==0)return 0;
        if(haystack.length()==0)return -1;
        if(needle.length()==0)return 0;
        KMP(needle);
        return search(haystack);
    }

    public void KMP(String pat) {
        this.pat = pat;
        int M = pat.length();
        // dp[状态][字符] = 下个状态
        dp = new int[M][256];
        // base case
        dp[0][pat.charAt(0)] = 1;
        // 影子状态 X 初始为 0。影子状态,也就是当前状态之前的那个状态。
        int X = 0;
        // 构建状态转移图(稍改的更紧凑了)
        for (int j = 1; j < M; j++) {
            for (int c = 0; c < 256; c++) {
                dp[j][c] = dp[X][c];//其实只有当c==j的时候,状态才会推进。其他情况的状态就交给上一个状态X,因为X已经计算过了
            }
            dp[j][pat.charAt(j)] = j + 1;
            
            // 更新影子状态。
            X = dp[X][pat.charAt(j)];
        }
    }

    public int search(String txt) {
        int M = pat.length();
        int N = txt.length();
        // pat 的初始态为 0
        int j = 0;
        for (int i = 0; i < N; i++) {
            // 计算 pat 的下一个状态
            j = dp[j][txt.charAt(i)];
            // 到达终止态,返回结果
            if (j == M) return i - M + 1;
        }
        // 没到达终止态,匹配失败
        return -1;
    }
}

  

posted @ 2020-12-23 16:05  A_Aron  阅读(249)  评论(0)    收藏  举报
//一下两个链接最好自己保存下来,再上传到自己的博客园的“文件”选项中