题解:学而思编程 气球游戏

【题目来源】

学而思编程:气球游戏

【题目描述】

小猴在进行射击气球的游戏,如果小猴在连续 \(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

【核心思想】

  1. 问题分析:给定长度为 \(n\) 的颜色序列 \(a_1, a_2, \ldots, a_n\)\(0\) 表示未打中),求包含所有 \(m\) 种颜色的最短连续子数组长度。这是一个滑动窗口(双指针)问题,关键在于维护一个满足"包含全部 \(m\) 种颜色"的窗口,并在满足条件时从左端收缩以寻找最小长度。

  2. 算法选择

    • 滑动窗口:用队列维护当前窗口内的元素,右指针扩展窗口,左指针在满足条件时收缩
    • 颜色计数数组cnt[c] 记录颜色 \(c\) 在当前窗口内的出现次数,color_num 记录窗口内不同颜色的种类数
    • 贪心收缩:当窗口包含所有 \(m\) 种颜色时,尝试从队首移除冗余元素(重复颜色或 \(0\)),以最小化窗口长度
  3. 关键步骤

    • 初始化:读取 \(n\)(子弹数)和 \(m\)(颜色种类数),读取颜色序列 \(a[1..n]\)
    • 右指针扩展(遍历 \(i\)\(1\)\(n\)):
      • q.push(a[i]):将当前颜色入队
      • a[i] != 0cnt[a[i]] == 0color_num++(新颜色加入窗口)
      • cnt[a[i]]++:更新该颜色计数
    • 左指针收缩(当 color_num == m 时):
      • t = q.front():获取队首颜色
      • 当队列非空且(cnt[t] > 1t == 0)时循环:
        • cnt[t]--:队首颜色计数减 \(1\)
        • q.pop():队首出队
        • t = q.front():更新队首
      • minn = \min(minn, q.size()):更新最小窗口长度
    • 判断输出
      • color_num == m:输出 minn
      • 否则:输出 \(-1\)(无法打爆所有颜色)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n)\),每个元素最多入队、出队各一次
    • 空间复杂度:\(O(n + m)\),队列最多存储 \(n\) 个元素,颜色计数数组 cnt[0..m]
  5. 滑动窗口的核心思想

    • 窗口不变性:始终保持窗口内包含所有 \(m\) 种颜色(当 color_num == m 时),在此基础上尝试收缩
    • 冗余元素识别:队首元素可移除的条件是其计数 \(> 1\)(该颜色在窗口内仍有其他出现)或是 \(0\)(未打中,不影响颜色覆盖),这种贪心收缩不会丢失更优解
    • 单调性:右指针只向右移动,左指针只向左收缩,每个元素最多被处理两次
    • \(0\) 的特殊处理\(0\) 表示未打中,不贡献任何颜色,在收缩时可直接跳过,不影响 color_num
    • 适用于"最短覆盖子数组"类问题,滑动窗口将 \(O(n^2)\) 的枚举优化为 \(O(n)\) 的线性扫描

【解题思路】

【算法标签】

双指针

【代码详解】

#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
posted @ 2026-07-04 14:32  团爸讲算法  阅读(3)  评论(0)    收藏  举报