题解:洛谷 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

【核心思想】

  1. 问题分析:给定长度为 \(N\) 的字符串 \(S\) 和子串长度 \(K\),需要统计所有长度为 \(K\) 的子串的出现次数,找出最大出现次数 \(x\),并按字典序输出所有出现次数为 \(x\) 的子串。这是一个字符串哈希/映射统计问题,关键在于高效提取子串并统计频率,利用 map 的自动排序特性简化后续处理。

  2. 算法选择

    • 滑动窗口提取子串:遍历 \(i\)\(1\)\(N-K+1\),用 substr(i, K) 提取子串
    • 哈希映射统计频率map<string, int> 自动按字典序存储子串,\(O(\log M)\) 完成插入与计数(\(M\) 为不同子串数)
    • 两次遍历筛选:第一遍找最大值,第二遍收集并输出所有频率等于最大值的子串
  3. 关键步骤

    • 读取输入:读入 \(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 本身已按字典序,但不同子串的首次出现位置无序,需显式排序保证字典序),输出所有结果
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \cdot K \cdot \log M)\),提取 \(N\) 个子串各 \(O(K)\)map 操作 \(O(\log M)\),排序 \(O(M \log M)\)
    • 空间复杂度:\(O(M \cdot K)\),存储所有不同子串及其频率
  5. 字符串哈希统计的核心思想

    • 子串即键:将长度为 \(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
posted @ 2026-08-27 11:03  团爸讲算法  阅读(8)  评论(0)    收藏  举报