重点记录
dp[j][c] = next
0 <= j < M,代表当前的状态
0 <= c < 256,代表遇到的字符(ASCII 码)
0 <= next <= M,代表下一个状态
dp[4]['A'] = 3 表示:
当前是状态 4,如果遇到字符 A,
pat 应该转移到状态 3
dp[1]['B'] = 2 表示:
当前是状态 1,如果遇到字符 B,
pat 应该转移到状态 2
search方法,用于根据dp数组查找txt字符串中是否存在目标子串
KMP 算法永不回退 txt 的指针 i,不走回头路(不会重复扫描 txt),而是借助 dp 数组中储存的信息把 pat 移到正确的位置继续匹配,时间复杂度只需 O(N)。
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++) {
// 当前是状态 j,遇到字符 txt[i],
// pat 应该转移到哪个状态?
j = dp[j][txt.charAt(i)];
// 如果达到终止态,返回匹配开头的索引
if (j == M) return i - M + 1;
}
// 没到达终止态,匹配失败
return -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)];
// dp[x][pat.charAt(j)]只有两种结果,一是X前进一位,例如ABCABC中B->C
// 另一个结果是回退,例如ABCABDCBCB中 charAt(j)=D,此时X=2-->X=0
}
}
public int search(String txt) {
...}
}
作者:labuladong
链接:https://leetcode-cn.com/problems/implement-strstr/solution/kmp-suan-fa-xiang-jie-by-labuladong/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
关于影子状态X:虽然不能都匹配上, 但截掉前面部分,后面还可以部分匹配上
影子状态的前缀:从0下标处到影子状态X处, 当前位置的前缀
: 当前位置J到影子状态X之间 两个前缀要吻合,J退后一个状态身位,截取影子状态的前缀 匹配当前的被匹配字符串。
ABABA… -------> 母串
ABABC ------> 模式串,
(如果模式串是CABABC,母串是CABABA?)
当要匹配的是 C 和 A 时,X = 2,恰巧母串为A,此时可将模式串匹配位置后移一个状态,如果母串为D,模式串匹配位置则继续回退到X=0处
LeetCode 28
class Solution {
public int strStr(String haystack, String needle) {
// return haystack.indexOf(needle);
int N = needle.length();
if (N == 0) return 0;
int[][] dp = new int[N][256];
KMP(needle, dp);
return search(haystack,dp);
}
public void KMP(String pattern, int[][] dp) {
// 初始化0状态处值,除了下面情况为1,其他默认都是0
dp[0][pattern.charAt(0)] = 1;
// 初始化影子状态值
int X = 0;
// 构造dp数组
for (int i = 1; i < dp.length; i++) {
for (int j = 0; j < 256; j++) {
// 大部分回退情况都会委托影子状态处理
dp[i][j] = dp[X][j];
}
dp[i][pattern.charAt(i)] = i + 1;
// 更新影子状态
X = dp[X][pattern.charAt(i)];
}
}
public int search(String haystack, int[][] dp) {
int N = haystack.length();
int M = dp.length;
int j = 0;
for (int i = 0; i < N; i++) {
// 遍历haystack字符串
j = dp[j][haystack.charAt(i)];
if(j == M) return i - M + 1;
}
return -1;
}
}