KMP字符匹配算法
参考这篇讲解
实现 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=2
,x
变成了状态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;
}
}