突破字符串匹配难题!今日深入解析KMP算法核心思想,从暴力匹配到next数组优化,彻底掌握高效字符串匹配的实现原理与代码技巧。
一、KMP算法核心思想
KMP算法(Knuth-Morris-Pratt)是高效的字符串匹配算法,其核心思想:
当模式串与主串不匹配时,利用已匹配的部分信息跳过不必要的比较,避免暴力匹配的完全回溯。
算法特性:
-
时间复杂度:O(n + m)(主串长n,模式串长m)
-
空间复杂度:O(m)(存储部分匹配表)
-
核心组件:部分匹配表(next数组)
二、算法原理与步骤分解
1. 部分匹配表(next数组)
定义:
next[i]
表示模式串P[0...i]
中,最长相等前后缀的长度
示例:
模式串"ABABC"
的next数组为[0,0,1,2,0]
2. 匹配流程
-
初始化主串指针
i=0
,模式串指针j=0
-
比较
S[i]
与P[j]
:-
匹配成功:
i++
,j++
-
匹配失败:根据
next[j-1]
回退j
-
-
当
j == m
时匹配成功,否则失败
三、C++代码实现
1. next数组构建(核心)
vector<int> buildNext(string pattern) {
int m = pattern.size();
vector<int> next(m, 0);
int j = 0; // 前缀末尾指针
for (int i = 1; i < m; ++i) {
// 前后缀不相同,向前回退
while (j > 0 && pattern[i] != pattern[j]) {
j = next[j - 1];
}
// 前后缀相同,j后移
if (pattern[i] == pattern[j]) {
j++;
}
next[i] = j;
}
return next;
}
2. KMP匹配主体
int kmpSearch(string text, string pattern) {
vector<int> next = buildNext(pattern);
int n = text.size(), m = pattern.size();
int j = 0; // 模式串指针
for (int i = 0; i < n; ++i) {
// 不匹配时利用next数组回退
while (j > 0 && text[i] != pattern[j]) {
j = next[j - 1];
}
// 当前字符匹配成功
if (text[i] == pattern[j]) {
j++;
}
// 完全匹配
if (j == m) {
return i - m + 1; // 返回起始位置
}
}
return -1;
}
// 测试用例
int main() {
string text = "ABABDABACDABABCABAB";
string pattern = "ABABCABAB";
cout << "匹配起始位置:" << kmpSearch(text, pattern); // 输出10
return 0;
}
四、复杂度分析
操作 | 时间复杂度 | 空间复杂度 |
---|---|---|
构建next数组 | O(m) | O(m) |
匹配过程 | O(n) | O(1) |
总计 | O(n+m) | O(m) |
五、大厂真题实战
真题1:重复子字符串(LeetCode 459)
题目描述:
判断字符串s
是否由多个重复子串构成
KMP巧解:
bool repeatedSubstringPattern(string s) {
int n = s.size();
vector<int> next = buildNext(s);
return next.back() != 0 && n % (n - next.back()) == 0;
}
真题2:最短回文串(LeetCode 214)
题目描述:
在字符串s
前添加字符使其成为回文串,求最短结果
KMP变种解法:
string shortestPalindrome(string s) {
string rev = s;
reverse(rev.begin(), rev.end());
string temp = s + "#" + rev;
vector<int> next = buildNext(temp);
return rev.substr(0, rev.size() - next.back()) + s;
}
六、常见误区与优化
-
next数组索引混乱:注意数组从0开始还是1开始
-
回退逻辑错误:
while
循环中必须持续回退 -
边界条件处理:空字符串或单字符模式串的特殊处理
-
空间优化:可合并next数组构建与匹配过程
-
字符集扩展:支持Unicode需改用哈希表存储
七、总结与扩展
核心要点:
-
next数组的构建是算法核心,体现自匹配思想
-
匹配过程利用已匹配信息避免冗余比较
-
KMP的扩展应用远超字符串匹配(如循环节判断)
扩展思考:
-
如何修改算法实现多模式匹配?
-
KMP与Boyer-Moore算法的性能对比?
-
如何利用KMP求字符串的周期?
LeetCode真题练习: