字符串合集I
1. 前缀函数与 KMP 算法
1.1 前缀函数
1.1.1 定义
给定一个长度为
用式子表示如下:
1.1.2 朴素求解
比较显然的
bool check(int l1, int r1, int l2, int r2) {
for (int i = l1, j = l2; i <= r1, j <= r2; i++, j++)
if (s[i] != s[j])
return 0;
return 1;
}
for (int i = 2; i <= n; i++)
for (int j = i; j >= 1; j--)
if (check(1, j, i - j + 1, i)) {
pi[i] = j;
break;
}
1.1.3 优化一
我们通过观察
bool check(int l1, int r1, int l2, int r2) {
for (int i = l1, j = l2; i <= r1, j <= r2; i++, j++)
if (s[i] != s[j])
return 0;
return 1;
}
for (int i = 2; i <= n; i++)
for (int j = pi[i - 1] + 1; j >= 1; j--)
if (check(1, j, i - j + 1, i)) {
pi[i] = j;
break;
}
1.1.4 优化二
假设我们已经求出了
-
首先令
,这表示 s[1 p + 1] 的最长相等前缀后缀一定由 s[1 p],的相等前缀后缀扩展而来,我们先尝试最长的那个相等前缀后缀。 -
尝试匹配
和 ,若相等,则得到 。 -
若不满足条件,我们找次长的相等前缀后缀,也就是
,再回到上一步匹配后面一个字符。
此时我们的时间复杂度变为了
for (int i = 2, p = 0; i <= n; i++) {
while (p && s[p + 1] != s[i]) p = pi[p];
pi[i] = p += s[p + 1] == s[i];
}
1.2 应用
1.2.1 Knuth-Morris-Pratt 算法
for (int i = 2, p = 0; i <= m; i++) {
while (p && t[p + 1] != t[i]) p = nxt[p];
nxt[i] = p += t[p + 1] == t[i];
}
for (int i = 1, p = 0; i <= n; i++) {
while (p && t[p + 1] != s[i]) p = nxt[p];
if (t[p + 1] == s[i]) p++;
if (p == m) cout << i - m + 1 << '\n', p = nxt[p];
}
1.2.2 字符串的周期
对字符串 s 和 0 < p \le |s|,若 s[i] = s[i+p] 对所有 i \in [0, |s| - p - 1] 成立,则称 p 是 s 的周期。
对字符串 s 和 0 \le r < |s|,若 s 长度为 r 的前缀和长度为 r 的后缀相等,就称 s 长度为 r 的前缀是 s 的 border。
由 s 有长度为 r 的 border 可以推导出 |s|-r 是 s 的周期。
根据前缀函数的定义,可以得到 s 所有的 border 长度,即 \pi[n-1],\pi[\pi[n-1]-1], \ldots。2
所以根据前缀函数可以在 O(n) 的时间内计算出 s 所有的周期。其中,由于 \pi[n-1] 是 s 最长 border 的长度,所以 n - \pi[n-1] 是 s 的最小周期。
1.2.4 KMP 自动机
本文来自博客园,作者:zhou_ziyi,转载请注明原文链接:https://www.cnblogs.com/zhouziyi/p/string.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具