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]以继续搜索。
-
这个实现高效且正确,能够处理所有边界情况(如空串、无匹配、多次匹配等)。
