【算法刷题】蓝桥杯/AtCoder:删除元素后的中位数问题(Symmetry / Median)
📌 题目描述与考点
- 问题核心 :给定一个包含 NNN 个元素的数组(NNN 为偶数)。依次计算删去第 iii 个元素 XiX_iXi 后,剩余 N−1N-1N−1 个元素组成的数组的中位数,并按原始顺序输出这 NNN 个答案。
- 核心考点 :中位数性质 、思维转换 、原序与排序序列的分离处理。
🔍 核心思路解析
1. 经典错误思路剖析
❌ 错误做法 1 :对于每个 XiX_iXi,都复制一份数组、删除 XiX_iXi、重新排序找中位数。
- 耗时 :每次排序耗时 O(NlogN)\mathcal{O}(N \log N)O(NlogN),总共 NNN 次,时间复杂度高达 O(N2logN)\mathcal{O}(N^2 \log N)O(N2logN),遭遇 TLE(超时)。
❌ 错误做法 2 :直接按原数组索引前 N/2N/2N/2 个输出较大中位数,后 N/2N/2N/2 个输出较小中位数。
- 原因 :忽略了原数组的输入顺序是无序的,删除元素的影响取决于该元素在数值大小排序后的位置,而非输入顺序。
2. 正确的数学推导(分类讨论)
将原数组按数值升序排序,设排序后的数组为 AAA:
- 原数组中第 N/2N/2N/2 小的数为 AN/2−1AN/2 - 1AN/2−1(记为
later)。 - 原数组中第 N/2+1N/2 + 1N/2+1 小的数为 AN/2AN/2AN/2(记为
former)。
对于任意被删除的元素 XiX_iXi(对应原输入数组 BiBiBi):
-
若 Xi≤AN/2−1X_i \le AN/2 - 1Xi≤AN/2−1(即 XiX_iXi 属于排序后数组的左半部分):
- 删掉 XiX_iXi 后,左半边少了一个小元素,中位数指针必须向右偏移补位。
- 新的中位数变为较大的那个数:
former(即 AN/2AN/2AN/2)。
-
若 Xi≥AN/2X_i \ge AN/2Xi≥AN/2(即 XiX_iXi 属于排序后数组的右半部分):
- 删掉 XiX_iXi 后,右半边少了一个大元素,中位数指针不受影响。
- 新的中位数变为较小的那个数:
later(即 AN/2−1AN/2 - 1AN/2−1)。
💻 C++ 完整 AC 代码
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
// 开启快速 I/O
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
if (!(cin >> n)) return 0;
vector<int> a(n); // 用于升序排序
vector<int> b(n); // 维护原始输入顺序
for (int i = 0; i < n; i++) {
cin >> a[i];
b[i] = a[i];
}
// 升序排序
sort(a.begin(), a.end());
// 提取排序后的两大关键中位数边界
int later = a[n / 2 - 1]; // 较小的中位数候选
int former = a[n / 2]; // 较大的中位数候选
// 按原始输入顺序判断并输出
for (int i = 0; i < n; i++) {
if (b[i] <= later) {
cout << former << "\n";
} else {
cout << later << "\n";
}
}
return 0;
}
❌ 易错点总结
- 混淆原始顺序与排序后顺序 :必须使用两个数组,一个数组 bbb 保留原始输入顺序进行遍历判断,另一个数组 aaa 排序提取中位数界限。
- 边界条件判断 :分支条件为
b[i] <= later(或b[i] < former),注意相等时的归属处理。
⏱️ 复杂度分析
- 时间复杂度 :O(NlogN)\mathcal{O}(N \log N)O(NlogN)
仅需在开始时对数组进行一次排序 O(NlogN)\mathcal{O}(N \log N)O(NlogN),后续输出只需要遍历一次数组 O(N)\mathcal{O}(N)O(N),轻松 AC。 - 空间复杂度 :O(N)\mathcal{O}(N)O(N)
需要两个大小为 NNN 的vector数组存储原始数据和排序数据。