题解:洛谷 AT_abc428_b Most Frequent Substrings
【题目来源】
洛谷:AT_abc428_b [ABC428B] Most Frequent Substrings - 洛谷
【题目描述】
给你一个长度为 \(N\) 的由小写字母构成的字符串 \(S\)。
定义长度为 \(K\) 的字符串 \(t\) 的出现次数为满足以下条件的整数 \(i\) 的个数:
- \(1\le i\le N-K+1\)
- \(S\) 的第 \(i\) 个到第 \(i+K-1\) 个字母构成的子串和 \(t\) 相同。
求长度为 \(K\) 的字符串出现次数的最大值 \(x\)。你还需要按字典序升序的顺序输出所有出现次数为 \(x\) 的长度为 \(K\) 的字符串。
【输入】
第一行两个整数 \(N,K\)。
第二行一个字符串 \(S\)。
【输出】
第一行输出一个整数,表示长度为 \(K\) 的字符串出现次数的最大值 \(x\)。
第二行按字典序升序的顺序输出所有出现次数为 \(x\) 的长度为 \(K\) 的字符串,用空格隔开。
【输入样例】
9 3
ovowowovo
【输出样例】
2
ovo owo
【核心思想】
-
问题分析:给定长度为 \(N\) 的字符串 \(S\) 和子串长度 \(K\),需要统计所有长度为 \(K\) 的子串的出现次数,找出最大出现次数 \(x\),并按字典序输出所有出现次数为 \(x\) 的子串。这是一个字符串哈希/映射统计问题,关键在于高效提取子串并统计频率,利用
map的自动排序特性简化后续处理。 -
算法选择:
- 滑动窗口提取子串:遍历 \(i\) 从 \(1\) 到 \(N-K+1\),用
substr(i, K)提取子串 - 哈希映射统计频率:
map<string, int>自动按字典序存储子串,\(O(\log M)\) 完成插入与计数(\(M\) 为不同子串数) - 两次遍历筛选:第一遍找最大值,第二遍收集并输出所有频率等于最大值的子串
- 滑动窗口提取子串:遍历 \(i\) 从 \(1\) 到 \(N-K+1\),用
-
关键步骤:
- 读取输入:读入 \(N, K\) 和字符串 \(S\),在 \(S\) 前补空格使下标从 \(1\) 开始
- 子串统计(\(i\) 从 \(1\) 到 \(N-K+1\)):
- 提取子串:\(t = S.substr(i, K)\)
- 频率累加:\(mp[t] \mathrel{+}= 1\)
- 求最大频率:遍历 \(mp\),\(maxn = \max(maxn, mp[t])\)
- 收集答案:再次遍历 \(mp\),将 \(mp[t] = maxn\) 的子串存入数组 \(ans\)
- 排序输出:对 \(ans\) 数组排序(
map本身已按字典序,但不同子串的首次出现位置无序,需显式排序保证字典序),输出所有结果
-
时间/空间复杂度:
- 时间复杂度:\(O(N \cdot K \cdot \log M)\),提取 \(N\) 个子串各 \(O(K)\),
map操作 \(O(\log M)\),排序 \(O(M \log M)\) - 空间复杂度:\(O(M \cdot K)\),存储所有不同子串及其频率
- 时间复杂度:\(O(N \cdot K \cdot \log M)\),提取 \(N\) 个子串各 \(O(K)\),
-
字符串哈希统计的核心思想:
- 子串即键:将长度为 \(K\) 的子串直接作为哈希键,利用字符串的唯一性实现自动去重与计数
- map的有序性:
std::map按字典序维护键,天然支持最终的字典序输出需求 - 两次遍历分离关注点:第一次聚焦"找最值",第二次聚焦"收集答案",降低代码耦合度
- 适用于"子串频率统计 + 最值筛选"类问题,核心是选择合适的字符串存储结构
【算法标签】
普及- #其他排序
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int n; // 字符串总长度
int k; // 子串长度
int maxn = -1e9; // 最大出现次数(初始化为极小值)
string s; // 输入字符串
string ans[105]; // 存储出现次数最多的子串
int cur = 0; // 记录结果子串的数量
map<string, int> mp; // 用于统计子串出现次数的映射表
int main()
{
// 输入字符串长度、子串长度和字符串
cin >> n >> k;
cin >> s;
// 在字符串前添加空格,使索引从1开始
s = " " + s;
// 提取所有长度为k的子串并统计出现次数
for (int i = 1; i <= n - k + 1; i++)
{
string t = s.substr(i, k); // 提取从位置i开始长度为k的子串
mp[t]++; // 增加该子串的计数
}
// 找出子串的最大出现次数
for (auto x : mp)
{
maxn = max(maxn, x.second); // 更新最大出现次数
}
// 输出最大出现次数
cout << maxn << endl;
// 收集所有出现次数等于最大值的子串
for (auto x : mp)
{
if (x.second == maxn)
{
ans[++cur] = x.first; // 保存符合条件的子串
}
}
// 对结果子串按字典序排序
sort(ans + 1, ans + cur + 1);
// 输出所有出现次数最多的子串
for (int i = 1; i <= cur; i++)
{
cout << ans[i] << " ";
}
cout << endl;
return 0;
}
【运行结果】
9 3
ovowowovo
2
ovo owo
浙公网安备 33010602011771号