NC149KMP算法详解
import java.util.*;public class Solution {public int[] BuildPMT(String pattern) {if (pattern == null || pattern.length() == 0) {return new int[0];}int[] pmt = new int[pattern.length()];pmt[0] = 0; // 第一个字符的PMT值总是0int len = 0;for (int i = 1; i < pattern.length(); ) {if (pattern.charAt(i) == pattern.charAt(len)) {len++;pmt[i] = len;i++;} else {if (len != 0) {len = pmt[len - 1]; // 回退,不是设置pmt[len]} else {pmt[i] = 0;i++;}}}return pmt;}public int kmp(String S, String T) {if (S == null || S.isEmpty() || T == null || T.isEmpty()) {return 0;}int[] pmt = BuildPMT(S);int count = 0;int j = 0; // pattern指针int i = 0; // text指针while (i < T.length()) {if (T.charAt(i) == S.charAt(j)) {i++;j++;if (j == S.length()) {count++;j = pmt[j - 1]; // 匹配成功后回退}} else {if (j != 0) {j = pmt[j - 1];} else {i++;}}}return count;}
}
1. BuildPMT
方法 - 构建部分匹配表
public int[] BuildPMT(String pattern) {if (pattern == null || pattern.length() == 0) {return new int[0];}
-
功能:检查输入参数是否有效。如果模式串
pattern
为null
或空字符串,返回空数组。
int[] pmt = new int[pattern.length()];pmt[0] = 0; // 第一个字符的PMT值总是0int len = 0;
-
功能:初始化部分匹配表
pmt
,其长度与模式串相同。 -
pmt[0] = 0
:单个字符的最长公共前后缀长度为0。 -
len
:记录当前最长公共前后缀长度。
for (int i = 1; i < pattern.length(); ) {if (pattern.charAt(i) == pattern.charAt(len)) {len++;pmt[i] = len;i++;}
-
功能:当字符匹配时,更新
pmt
表。 -
i
从1开始,因为pmt[0]
已经确定为0。 -
如果
pattern[i] == pattern[len]
,说明当前字符可以扩展公共前后缀:-
len++
:公共前后缀长度+1。 -
pmt[i] = len
:记录当前位置的最长公共前后缀长度。 -
i++
:移动到下一个字符。
-
else {if (len != 0) {len = pmt[len - 1]; // 回退到前一个字符的PMT值} else {pmt[i] = 0;i++;}}}return pmt;
}
-
功能:当字符不匹配时,回退
len
。 -
如果
len != 0
,回退到pmt[len - 1]
(利用已计算的PMT值避免重复比较)。 -
如果
len == 0
,说明无法回退,pmt[i] = 0
,并移动到下一个字符i++
。
2. kmp
方法 - 实现KMP搜索
public int kmp(String S, String T) {if (S == null || S.isEmpty() || T == null || T.isEmpty()) {return 0;}
-
功能:检查输入参数是否有效。如果模式串
S
或文本串T
为空,直接返回0。
int[] pmt = BuildPMT(S);int count = 0;int j = 0; // 模式串S的指针int i = 0; // 文本串T的指针
-
功能:初始化变量。
-
pmt
:构建模式串S
的部分匹配表。 -
count
:记录S
在T
中出现的次数。 -
j
:指向模式串S
的当前字符。 -
i
:指向文本串T
的当前字符。
while (i < T.length()) {if (T.charAt(i) == S.charAt(j)) {i++;j++;if (j == S.length()) {count++;j = pmt[j - 1]; // 匹配成功后回退}}
-
功能:字符匹配时的处理。
-
如果
T[i] == S[j]
,则同时移动i
和j
。 -
如果
j == S.length()
,说明找到一个完整匹配:-
count++
:增加匹配计数。 -
j = pmt[j - 1]
:回退j
以继续搜索可能的其他匹配(避免遗漏重叠匹配)。
-
else {if (j != 0) {j = pmt[j - 1]; // 利用PMT回退j} else {i++; // 无法回退,移动i}}}return count;
}
-
功能:字符不匹配时的处理。
-
如果
j != 0
,回退j
到pmt[j - 1]
(利用PMT跳过不必要的比较)。 -
如果
j == 0
,无法回退,只能移动i
。
关键点总结
-
PMT表:
-
pmt[i]
表示模式串S[0..i]
的最长公共前后缀长度。 -
用于在匹配失败时快速回退
j
。
-
-
KMP搜索:
-
通过
pmt
表避免回溯i
,保证i
始终单向移动。 -
时间复杂度:
O(n + m)
(n
为文本串长度,m
为模式串长度)。
-
-
回退逻辑:
-
匹配失败时,
j
回退到pmt[j - 1]
。 -
完整匹配后,
j
回退到pmt[j - 1]
以继续搜索。
-
这个实现高效且正确,能够处理所有边界情况(如空串、无匹配、多次匹配等)。