题解:学而思编程 气球游戏
【题目来源】
学而思编程:气球游戏
【题目描述】
小猴在进行射击气球的游戏,如果小猴在连续 \(q\) 枪中打爆了所有颜色的气球,将得到一只阿噗公仔作为奖励。(每种颜色的球至少被打爆一只)。
这个游戏中有 \(m\) 种不同颜色的气球,编号 \(1\) 到 \(m\)。
小猴一共有 \(n\) 发子弹,然后连续开了 \(n\) 枪。
小猴想知道在这 \(n\) 枪中,打爆所有颜色的气球最少用了连续几枪?
【输入】
第一行两个整数 \(n\) 和 \(m\)。
第二行 \(n\) 个整数 \(a_1,a_2,\dots,a_n\),分别表示每一枪打中的气球的颜色,\(0\) 表示没打中任何颜色的气球。
【输出】
一行一个整数表示小猴打爆所有颜色气球用的最少枪数。
如果小猴无法在这 \(n\) 枪打爆所有颜色的气球,则输出 \(-1\)。
【输入样例】
12 5
2 5 3 1 3 2 4 1 0 5 4 3
【输出样例】
6
【核心思想】
-
问题分析:给定长度为 \(n\) 的颜色序列 \(a_1, a_2, \ldots, a_n\)(\(0\) 表示未打中),求包含所有 \(m\) 种颜色的最短连续子数组长度。这是一个滑动窗口(双指针)问题,关键在于维护一个满足"包含全部 \(m\) 种颜色"的窗口,并在满足条件时从左端收缩以寻找最小长度。
-
算法选择:
- 滑动窗口:用队列维护当前窗口内的元素,右指针扩展窗口,左指针在满足条件时收缩
- 颜色计数数组:
cnt[c]记录颜色 \(c\) 在当前窗口内的出现次数,color_num记录窗口内不同颜色的种类数 - 贪心收缩:当窗口包含所有 \(m\) 种颜色时,尝试从队首移除冗余元素(重复颜色或 \(0\)),以最小化窗口长度
-
关键步骤:
- 初始化:读取 \(n\)(子弹数)和 \(m\)(颜色种类数),读取颜色序列 \(a[1..n]\)
- 右指针扩展(遍历 \(i\) 从 \(1\) 到 \(n\)):
q.push(a[i]):将当前颜色入队- 若
a[i] != 0且cnt[a[i]] == 0:color_num++(新颜色加入窗口) cnt[a[i]]++:更新该颜色计数
- 左指针收缩(当
color_num == m时):t = q.front():获取队首颜色- 当队列非空且(
cnt[t] > 1或t == 0)时循环:cnt[t]--:队首颜色计数减 \(1\)q.pop():队首出队t = q.front():更新队首
minn = \min(minn, q.size()):更新最小窗口长度
- 判断输出:
- 若
color_num == m:输出minn - 否则:输出 \(-1\)(无法打爆所有颜色)
- 若
-
时间/空间复杂度:
- 时间复杂度:\(O(n)\),每个元素最多入队、出队各一次
- 空间复杂度:\(O(n + m)\),队列最多存储 \(n\) 个元素,颜色计数数组
cnt[0..m]
-
滑动窗口的核心思想:
- 窗口不变性:始终保持窗口内包含所有 \(m\) 种颜色(当
color_num == m时),在此基础上尝试收缩 - 冗余元素识别:队首元素可移除的条件是其计数 \(> 1\)(该颜色在窗口内仍有其他出现)或是 \(0\)(未打中,不影响颜色覆盖),这种贪心收缩不会丢失更优解
- 单调性:右指针只向右移动,左指针只向左收缩,每个元素最多被处理两次
- \(0\) 的特殊处理:\(0\) 表示未打中,不贡献任何颜色,在收缩时可直接跳过,不影响
color_num - 适用于"最短覆盖子数组"类问题,滑动窗口将 \(O(n^2)\) 的枚举优化为 \(O(n)\) 的线性扫描
- 窗口不变性:始终保持窗口内包含所有 \(m\) 种颜色(当
【解题思路】

【算法标签】
双指针
【代码详解】
#include <bits/stdc++.h>
using namespace std;
queue<int> q; // 滑动窗口队列
int a[1000010]; // 存储颜色数组
int cnt[2020]; // 颜色计数数组
int main()
{
int n, m;
cin >> n >> m; // 输入总长度n和颜色种类数m
for (int i = 1; i <= n; i++) // 输入颜色序列
{
cin >> a[i];
}
int color_num = 0; // 当前窗口内不同颜色的数量
int minn = 1e9; // 最小窗口长度
for (int i = 1; i <= n; i++) // 遍历数组
{
q.push(a[i]); // 入队
if (a[i] != 0 && cnt[a[i]] == 0) // 如果是新颜色
{
color_num++; // 颜色计数增加
}
cnt[a[i]]++; // 颜色计数
if (color_num == m) // 如果窗口包含所有m种颜色
{
int t = q.front(); // 获取队首元素
// 尝试缩小窗口:当队首颜色计数>1或是0时,可以出队
while (!q.empty() && (cnt[t] > 1 || t == 0))
{
cnt[t]--; // 计数减少
q.pop(); // 出队
t = q.front(); // 更新队首
}
minn = min(int(q.size()), minn); // 更新最小窗口长度
}
}
if (color_num == m) // 如果找到了包含所有颜色的窗口
{
cout << minn << endl;
}
else
{
cout << -1 << endl; // 没有找到
}
return 0;
}
【运行结果】
12 5
2 5 3 1 3 2 4 1 0 5 4 3
6
浙公网安备 33010602011771号