瑞瑞的环形花坛
【题目来源】
瑞学堂:瑞瑞的环形花坛
【题目描述】
瑞瑞在校园里设计了一个环形花坛,环上均匀分布着 \(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
【核心思想】
-
问题分析:给定环形数组 \(a[1..n]\)(\(a_i\) 表示花的种类),求长度不超过 \(\lfloor n/2 \rfloor\) 的连续子数组中,不同元素个数的最大值。这是一个环形数组 + 滑动窗口问题,关键在于将环形结构通过"复制翻倍"转化为线性数组,再用双指针维护长度受限的窗口。
-
算法选择:
- 环形转线性(复制翻倍):将数组复制为 \(a[1..2n]\),其中 \(a[i+n] = a[i]\),使得任意环形连续区间都对应线性数组上的一个连续子数组
- 滑动窗口(双指针):维护窗口 \([i, j]\),保证长度 \(j - i + 1 \leq \lfloor n/2 \rfloor\),用哈希/数组统计窗口内不同元素个数
- 长度限制处理:当窗口长度超过限制时,收缩左端点 \(i\)
-
关键步骤:
- 数组翻倍:读入 \(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]] == 0则res++,然后cnt[a[j]]++) - 更新答案:
ans = max(ans, res)
- 长度检查:若 \(j - i + 1 > \lfloor n/2 \rfloor\),则右移 \(i\)(
- 输出 \(ans\)
-
时间/空间复杂度:
- 时间复杂度:\(O(n)\),双指针每个位置最多被访问两次(入窗一次、出窗一次)
- 空间复杂度:\(O(n)\),翻倍数组 \(2n\) 和计数数组(离散化后种类数不超过 \(n\))
-
双指针 + 环形处理的核心思想:
- 环形转线性的经典技巧:复制数组实现 \(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
浙公网安备 33010602011771号