问题描述
给定一个长度为 n 的字符串 S ,幸运字符串的定义如下:
- 该字符串为 S 的一个前缀字符串 。
- 该字符串在 S 中至少出现过 2 次 。
现在要你求出长度最大的幸运字符串 。
输入格式
输入第一行,包含一个整数 n ,表示字符串的长度 。
输入第二行,长度为 n 且由小写字母组成的字符串 。
输出格式
输出仅一行,包含一个整数,表示长度最大的幸运字符串的长度 。
输入案例
9
abcdaaaba
样例输出
2
说明
前缀 ab 在 S 中出现了两次,由此答案是 2 。
解题思想:
该题目的本质就是求一个出现在字符串中的最大的前缀字符串,可以用 KMP 解决这个问题,KMP 的 next 数组存的是这个位置前面的字符串中前缀与后缀相等的最大长度,所以我们可以预处理 S的 next 数组,枚举每一个位置的 next 的值,取一个最大值即为答案 。
解题代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10; // 定义常量 N,表示字符串的最大长度,设置为 2e5 + 10 以应对题目中的输入范围
int nex[N]; // 定义 next 数组,用于存储 KMP 算法中的部分匹配表
// nex[i] 表示字符串前 i 个字符中,最长的既是前缀又是后缀的子串的长度
int main() {
int n; cin >> n; // 读取字符串的长度 n
char p[n + 1]; // 定义字符数组 p,长度为 n + 1,用于存储字符串(下标从 1 开始)
cin >> p + 1; // 从下标 1 开始读取字符串 S,存储在字符数组 p 中
// 构建 KMP 算法的 next 数组
for (int i = 2, j = 0; i <= n; i++) { // 从字符串的第二个字符开始遍历
while (j && p[i] != p[j + 1]) j = nex[j]; // 如果当前字符不匹配,则回退 j 到 nex[j],直到匹配或 j 为 0
if (p[i] == p[j + 1]) j++; // 如果当前字符匹配,则 j 增加 1,表示匹配长度增加
nex[i] = j; // 将当前匹配的长度 j 存入 nex[i],表示前 i 个字符中,最长的既是前缀又是后缀的子串的长度
}
int ans = 0; // 初始化 ans 为 0,用于存储最大的幸运字符串的长度
for (int i = 1; i <= n; i++) ans = max(ans, nex[i]); // 遍历 next 数组,找到其中的最大值
cout << ans << endl; // 输出结果,即最长的幸运字符串的长度
return 0;
}
代码说明:
1. 代码逻辑总结
-
输入处理:
-
读取字符串长度 nn。
-
定义字符数组
p
,长度为 n+1n+1,并从下标 1 开始读取字符串 SS。
-
-
KMP 算法的 next 数组构建:
-
通过遍历字符串,计算每个位置 ii 的最长前缀后缀匹配长度,并存储在
nex[i]
中。
-
-
求解最大幸运字符串:
-
遍历
nex
数组,找到最大值,即为最长的既是前缀又在字符串中至少出现过 2 次的子串的长度。
-
-
输出结果:
-
输出最大值
ans
。
-
在 KMP 算法中,i
从 2 开始,j
从 0 开始的原因与算法的核心思想密切相关。以下是详细解释:
2. i
从 2 开始的原因
-
i
表示当前正在处理的字符位置。 -
KMP 算法的 next 数组:
nex[i]
表示字符串的前 ii 个字符中,最长的既是前缀又是后缀的子串的长度。 -
为什么从 2 开始:
-
对于第一个字符(
i = 1
),它没有前缀和后缀,因此nex[1]
的值一定是 0。 -
从
i = 2
开始,才能有意义地计算前缀和后缀的匹配情况。 -
如果从
i = 1
开始,nex[1]
的值已经被默认设置为 0,无需再计算。
-
3. j
从 0 开始的原因
-
j
表示当前匹配的前缀长度。 -
初始状态:
-
当
i = 2
时,j
从 0 开始,表示还没有匹配到任何前缀。 -
j
的作用是记录当前匹配的前缀长度,并在匹配失败时回退到nex[j]
。
-
-
为什么从 0 开始:
-
j = 0
表示当前没有匹配到任何前缀。 -
如果
j
从 1 开始,会导致逻辑错误,因为j
表示的是已经匹配的长度,而不是当前字符的位置。
-
4. KMP 算法的核心逻辑
-
匹配过程:
-
当
p[i] == p[j + 1]
时,表示当前字符匹配成功,j
增加 1。 -
当
p[i] != p[j + 1]
时,表示当前字符匹配失败,j
回退到nex[j]
,继续尝试匹配。
-
-
nex
数组的作用:-
nex[j]
记录了当匹配失败时,j
应该回退到的位置。 -
通过
nex
数组,KMP 算法可以跳过不必要的比较,提高匹配效率。
-
5. 为什么 i
从 2 开始,j
从 0 开始的示例
假设字符串为 ababab
,构建 nex
数组的过程如下:
i | p[i] | j | p[j + 1] | 操作 | nex[i] |
---|---|---|---|---|---|
2 | b | 0 | a | p[2] != p[1] ,j 保持 0 | 0 |
3 | a | 0 | a | p[3] == p[1] ,j++ | 1 |
4 | b | 1 | b | p[4] == p[2] ,j++ | 2 |
5 | a | 2 | a | p[5] == p[3] ,j++ | 3 |
6 | b | 3 | b | p[6] == p[4] ,j++ | 4 |
-
最终
nex
数组为[0, 0, 1, 2, 3, 4]
。 -
最长的既是前缀又是后缀的子串是
abab
,长度为 4。