瑞瑞的环形花坛

【题目来源】

瑞学堂:瑞瑞的环形花坛

【题目描述】

瑞瑞在校园里设计了一个环形花坛,环上均匀分布着 \(n\) 个位置,编号为 \(1\) 到 \(n\),位置 \(i\) 与位置 \(i+1\) 相邻(位置 \(n\) 与位置 \(1\) 相邻)。每个位置可以种一棵花,花的种类用一个整数 \(a_i\) 表示,相同的整数代表同一种花。

瑞瑞想要选出一段连续的位置,使得这段位置里的花的种类数尽可能多。然而,由于环形花坛上的花形成环状,这段连续位置不能超过花坛半圈的长度,即所选区间的长度不能超过 \(\lfloor n/2\rfloor\)

请你帮瑞瑞计算,在满足长度限制的条件下,一段连续位置中最多能包含多少种不同的花。

【输入】

第一行一个整数 \(n\),表示环形花坛的位置数量。
第二行 \(n\) 个整数 \(a_1,a_2,…,a_n\),表示每个位置上花的种类。

【输出】

输出一行一个整数,表示最多包含的不同花的种类数。

【输入样例】

6
1 2 3 1 2 3

【输出样例】

3

【核心思想】

  1. 问题分析:给定环形数组 \(a[1..n]\)\(a_i\) 表示花的种类),求长度不超过 \(\lfloor n/2 \rfloor\) 的连续子数组中,不同元素个数的最大值。这是一个环形数组 + 滑动窗口问题,关键在于将环形结构通过"复制翻倍"转化为线性数组,再用双指针维护长度受限的窗口。

  2. 算法选择

    • 环形转线性(复制翻倍):将数组复制为 \(a[1..2n]\),其中 \(a[i+n] = a[i]\),使得任意环形连续区间都对应线性数组上的一个连续子数组
    • 滑动窗口(双指针):维护窗口 \([i, j]\),保证长度 \(j - i + 1 \leq \lfloor n/2 \rfloor\),用哈希/数组统计窗口内不同元素个数
    • 长度限制处理:当窗口长度超过限制时,收缩左端点 \(i\)
  3. 关键步骤

    • 数组翻倍:读入 \(a[1..n]\),令 \(a[i+n] = a[i]\)\(i\)\(1\)\(n\)),得到长度 \(2n\) 的线性数组
    • 初始化:双指针 \(i = 1, j = 1\),计数数组 cnt[] 记录各元素出现次数,res 记录当前窗口不同种类数
    • 滑动窗口\(j\)\(1\) 遍历到 \(2n\)):
      • 长度检查:若 \(j - i + 1 > \lfloor n/2 \rfloor\),则右移 \(i\)cnt[a[i]]--,若变为 \(0\)res--
      • 扩展右端点:将 \(a[j]\) 加入窗口(若 cnt[a[j]] == 0res++,然后 cnt[a[j]]++
      • 更新答案ans = max(ans, res)
    • 输出 \(ans\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n)\),双指针每个位置最多被访问两次(入窗一次、出窗一次)
    • 空间复杂度:\(O(n)\),翻倍数组 \(2n\) 和计数数组(离散化后种类数不超过 \(n\)
  5. 双指针 + 环形处理的核心思想

    • 环形转线性的经典技巧:复制数组实现 \(O(1)\) 的环形连续区间查询,避免取模运算和边界分裂讨论
    • 滑动窗口的长度约束:通过 while 循环严格维护窗口长度 \(\leq \lfloor n/2 \rfloor\),保证满足题意
    • 计数数组维护不同元素个数res 只在元素首次入窗(cnt\(0\)\(1\))和最后出窗(cnt\(1\)\(0\))时变化,实现 \(O(1)\) 更新
    • 双指针的单调性:右指针 \(j\) 只增不减,左指针 \(i\) 只增不减,确保线性时间复杂度
    • 适用于环形数组的子数组统计问题,核心在于"复制翻倍消环"和"双指针维护受限窗口"

【算法标签】

双指针

【代码详解】

#include <bits/stdc++.h>
using namespace std;
const int N = 100005;    // 定义数组最大容量为100005
int n, res, ans;         // n为环形花坛位置数,res记录当前窗口内不同花的种类数,ans记录最大种类数
int a[N * 2];            // 将环形数组复制一倍展开为线性数组,便于处理跨首尾的情况
int cnt[N];              // cnt[x]记录当前滑动窗口中花的种类x出现的次数
map<int, int> mp;        // mp未使用(原代码中用于离散化但直接用数组cnt替代了)

int main()
{
    cin >> n;            // 读入环形花坛的位置数量n
    for (int i = 1; i <= n; i++)  // 读入每个位置的花的种类
    {
        cin >> a[i];              // 读入第i个位置的花的种类
        a[i + n] = a[i];          // 将数组复制一倍:a[n+1]=a[1], a[n+2]=a[2], ...
    }
    int len = n / 2;     // 计算最大允许区间长度:不超过半圈,即floor(n/2)

    // 双指针滑动窗口:i为左端点,j为右端点
    for (int i = 1, j = 1; j <= 2 * n; j++)  // j从1遍历到2n,枚举所有可能的右端点
    {
        // 当窗口长度超过len时,收缩左端点i
        while (j - i + 1 > len)   // 如果当前窗口长度超过最大允许长度
        {
            --cnt[a[i]];          // 左端点元素a[i]移出窗口,出现次数减1
            if (cnt[a[i]] == 0)   // 如果a[i]的出现次数变为0
                res--;            // 窗口内不同种类数减1
            i++;                  // 左端点右移
        }
        // 将右端点元素a[j]加入窗口
        if (cnt[a[j]] == 0)       // 如果a[j]之前不在窗口中
            ++res;                // 窗口内不同种类数加1
        ++cnt[a[j]];              // a[j]的出现次数加1

        ans = max(ans, res);      // 更新最大种类数
    }
    cout << ans << endl;          // 输出满足长度限制的最大不同花种类数
    return 0;
}

【运行结果】

6
1 2 3 1 2 3
3
posted @ 2026-08-06 08:45  团爸讲算法  阅读(8)  评论(0)    收藏  举报