【动态规划】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;
}
}

浙公网安备 33010602011771号