题解:洛谷 P1381 单词背诵
【题目来源】
【题目描述】
灵梦有 \(n\) 个单词想要背,但她想通过一篇文章中的一段来记住这些单词。
文章由 \(m\) 个单词构成,她想在文章中找出连续的一段,其中包含最多的她想要背的单词(重复的只算一个)。并且在背诵的单词量尽量多的情况下,还要使选出的文章段落尽量短,这样她就可以用尽量短的时间学习尽可能多的单词了。
【输入】
第 \(1\) 行一个数 \(n\),接下来 \(n\) 行每行是一个长度不超过 \(10\) 的字符串,表示一个要背的单词。
接着是一个数 \(m\),然后是 \(m\) 行长度不超过 \(10\) 的字符串,每个表示文章中的一个单词。
【输出】
输出文件共 \(2\) 行。第 \(1\) 行为文章中最多包含的要背的单词数,第 \(2\) 行表示在文章中包含最多要背单词的最短的连续段的长度。
【输入样例】
3
hot
dog
milk
5
hot
dog
dog
milk
hot
【输出样例】
3
3
【核心思想】
-
问题分析:给定 \(n\) 个目标单词(需背诵)和一篇由 \(m\) 个单词组成的文章,需要从文章中找出一个连续段,使得该段中包含的目标单词种类数最多(重复的只计一次),并且在该条件下段的长度最短。输出最多能包含的目标单词种类数,以及对应的最短连续段长度。这是滑动窗口的经典应用,需要在窗口中动态维护目标单词的计数和种类数,并在窗口满足条件时尝试收缩左边界以优化长度。
-
算法选择:
- 双指针滑动窗口:维护一个窗口 \([i, j]\),用哈希表
cnt记录窗口内每个目标单词的出现次数,用变量sum记录窗口内不同目标单词的个数。当右指针j扩展时,若新单词是目标单词且首次出现,则sum++;当窗口包含目标单词时,尝试移动左指针i来收缩窗口(若左指针单词在窗口中出现次数 \(\ge 2\) 或不是目标单词则可移动),并在每次收缩后更新最短长度。最终sum即为最大包含种类数,len为对应的最短连续段长度。
- 双指针滑动窗口:维护一个窗口 \([i, j]\),用哈希表
-
关键步骤:
- 建立目标单词集合:用
map<string, bool> word标记所有需要背诵的单词。 - 读入文章并滑动窗口:初始化左指针
i = 1,右指针j = 1,sum = 0,len = INF。对于每个读入的文章单词s[j]:- 若
s[j]是目标单词,则cnt[s[j]]++,若cnt[s[j]] == 1则sum++,并初始化len = j - i + 1。 - 当
i <= j时,尝试收缩左边界:- 若左指针单词
s[i]在窗口中只出现一次(cnt[s[i]] == 1),则不能移动(否则会丢失该单词)。 - 若
cnt[s[i]] >= 2,则cnt[s[i]]--,i++。 - 若
s[i]不是目标单词,则直接i++。
- 若左指针单词
- 更新最短长度
len = min(len, j - i + 1)。
- 若
- 输出结果:第一行输出
sum(最多包含的目标单词种类数),第二行输出len(对应的最短连续段长度)。
- 建立目标单词集合:用
-
时间/空间复杂度:
- 时间复杂度:\(O(m)\),每个单词被左右指针各访问一次。
- 空间复杂度:\(O(n)\),用于存储目标单词集合和计数哈希表。
-
双指针 + 哈希表的核心思想:
- 滑动窗口:通过双指针维护一个动态窗口,利用哈希表快速判断单词是否为目标单词并统计其出现次数。
- 种类数统计:窗口内目标单词的种类数
sum随着右指针扩展而增加,随着左指针收缩而减少。 - 长度优化:在窗口包含目标单词时,通过移动左指针尽量缩短窗口长度,同时保证
sum不变(即不丢失任何目标单词种类)。 - 适用场景:适用于在序列中查找包含特定元素集合的最短连续子段或最大覆盖种类数问题。
【算法标签】
普及+ #双指针
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 100005; // 定义最大单词数量
int n, m; // n: 目标单词数量, m: 文章单词数量
int sum = 0, len = 0; // sum: 包含的目标单词种类数, len: 最短包含所有目标单词的段落长度
string s[MAX_N]; // 存储文章中的单词序列
map<string, bool> word; // 记录目标单词集合
map<string, int> cnt; // 记录当前窗口中各目标单词出现次数
int main()
{
// 输入目标单词
cin >> n;
for (int i = 1; i <= n; i++)
{
string s1;
cin >> s1;
word[s1] = true; // 标记为目标单词
}
// 输入文章单词并处理
cin >> m;
for (int i = 1, j = 1; j <= m; j++) // i: 窗口左边界, j: 窗口右边界
{
cin >> s[j]; // 输入当前单词
// 如果是目标单词,更新计数
if (word[s[j]])
{
cnt[s[j]]++;
// 如果是第一次出现该目标单词,更新种类数
if (cnt[s[j]] == 1)
{
sum++;
len = j - i + 1; // 初始化段落长度
}
}
// 调整窗口左边界
while (i <= j)
{
// 如果当前左边界单词只出现一次,不能移动
if (cnt[s[i]] == 1)
break;
// 如果左边界单词出现多次,可以右移
if (cnt[s[i]] >= 2)
{
cnt[s[i]]--;
i++;
}
// 如果左边界单词不是目标单词,直接右移
if (!word[s[i]])
i++;
}
// 更新最短段落长度
len = min(len, j - i + 1);
}
// 输出结果
cout << sum << endl << len;
return 0;
}
【运行结果】
3
hot
dog
milk
5
hot
dog
dog
milk
hot
3
3
浙公网安备 33010602011771号