leetcode刷题-- kmp字符串匹配算法(转)

KMP字符匹配算法

参考这篇讲解

例题28

实现 strStr() 函数。

给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回  -1。

示例 1:

输入: haystack = "hello", needle = "ll"
输出: 2
示例 2:

输入: haystack = "aaaaa", needle = "bba"
输出: -1

就是查找子串的问题,可以用两个指针来查找。

class Solution:
    def strStr(self, haystack: str, needle: str) -> int:
        if needle=="": return 0
        if len(needle)>len(haystack): return -1

        n_h = len(haystack)
        n_n = len(needle)
        i,j = 0,0

        while i<n_h:
            if haystack[i]==needle[j]:
                i += 1
                j += 1
            else:
                i = i - j + 1
                j = 0
            
            if j==n_n:
                return i-j
        return -1

KMP算法

这里的dp数组只与模式串有关,也即题目里的needle有关。

状态:

假设pat = "ABABC",根据之前动态规划的步骤,这里将每个数字看作一个状态。dp[i][c]表示在状态i时遇到c字符的时候。

这里的选择有:

  • 字符相等的话,例如一开始状态是0,如果遇到字符A,状态加1,状态前进一位。
  • 字符不等的话,那么状态就需要退回,或不动。

状态转移:

  • 相等, dp[i][c] = i + 1
  • 不等, dp[i][c] = dp[x][c]

主要理解x的意义。

这里的x定义为i之前的那个状态,不是指i-1。例如对照代码:

public class KMP {
    private int[][] dp;
    private String pat;

    public 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;
        // 当前状态 j 从 1 开始
        for (int j = 1; j < M; j++) {
            for (int c = 0; c < 256; c++) {
                if (pat.charAt(j) == c) 
                    dp[j][c] = j + 1;
                else 
                    dp[j][c] = dp[X][c];
            }
            // 更新影子状态
            X = dp[X][pat.charAt(j)];
        }
    }

    public int search(String txt) {...}
}

假设pat=abcabd,那么当j=3时,也就是再次扫到字符a时,x才会加一,前进一个状态。即,一开始x=0,一直到j=3,x = dp[x][char[j]],我们可知dp[0]['a']=1,所以x=1。同理j=4时,因为状态dp[1]['b']=2也就是从状态1遇到字符b就变成状态2,x=2x变成了状态2。

那么当遇到字符d时,就能退回到状态2。再来搜索。

public class KMP {
    private int[][] dp;
    private String pat;

    public 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];
            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;
    }
}

猜你喜欢

转载自www.cnblogs.com/ivan-blog/p/12409850.html