维护动态区间的中位数
问题描述
依次读入一个整数序列,每当已经读入的整数个数为奇数时,输出已读入的整数构成的序列的中位数。详细内容
sort
最朴素写法,每到奇数时位将前面所有数据排序,找到中位数
每次sort是,一组数据需要sort 次,所以复杂度为
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 10010;
int a[M];
int main()
{
int p;
cin >> p;
while (p --)
{
int n, m;
cin >> n >> m;
cout << n << ' ' << (m + 1 >> 1) << endl;
int cnt = 0;
for (int i = 1; i <= m; ++ i)
{
cin >> a[i];
if (i % 2 != 0)
{
sort(a + 1, a + i + 1);
cout << a[1 + i >> 1] << ' ';
if (++ cnt % 10 == 0) cout << endl;
}
}
if (cnt % 10) cout << endl; // 解决最后一行数据不够10个,上方未输出空行,会导致下面组的格式出问题
}
return 0;
}
快速选择
寻找中位数,上述做法采用的快速排序,也可以采用O(n)的快速选择
所以每组的复杂度就变为了
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 10010;
int a[M];
int qsort(int a[], int l, int r, int k)
{
if (l >= r) return a[l];
int x = a[l + r >> 1], i = l - 1, j = r + 1;
while (i < j)
{
while (a[++ i] < x);
while (a[-- j] > x);
if (i < j) swap(a[i], a[j]);
}
int sl = j - l + 1;
if (k <= sl) return qsort(a, l, j, k);
return qsort(a, j + 1, r, k - sl);
}
int main()
{
int p;
cin >> p;
while (p --)
{
int n, m;
cin >> n >> m;
cout << n << ' ' << (m + 1 >> 1) << endl;
int cnt = 0;
for (int i = 1; i <= m; ++ i)
{
cin >> a[i];
if (i % 2 != 0)
{
cout << qsort(a, 1, i, 1 + i >> 1) << ' ';
if (++ cnt % 10 == 0) cout << endl;
}
}
if (cnt % 10) cout << endl; // 解决最后一行数据不够10个,上方未输出空行,会导致下面组的格式出问题
}
return 0;
}
对顶堆
应用场景
维护动态区间的中位数
参照剑指offer中的描述对图片进行修改
小根堆始终维护大于大根堆堆顶元素的元素,大根堆维护小于大根堆堆顶元素的元素,由于我们保证大根堆元素个数最多比小根堆元素个数多1,所以在奇数个元素的情况下,两个堆的元素数量不会相等,一定是相差1,大根堆堆顶元素恰好位于中位数的位置,每个数据插入堆中是的,所以每组的时间复杂度为
之所以能够想到这样的解法,是因为我们确定中位数其实并不需要保证其左右两侧数据的有序性,只要左侧数据都小,右侧数据都大即可,使用两个堆恰好能够维护这种性质
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
int main()
{
int p;
cin >> p;
while (p --)
{
int n, m;
cin >> n >> m;
cout << n << ' ' << (m + 1 >> 1) << endl;
priority_queue<int> down; // 下方的大根堆
priority_queue<int, vector<int>, greater<int>> up; // 上方的小根堆
int cnt = 0;
for (int i = 1; i <= m; ++ i)
{
int x;
cin >> x;
if (down.empty() || x < down.top()) down.push(x); // 这里用<或<=均可,因为我们只需要保证中位数的相对位置正确即可
else up.push(x);
/**
* 这里只是规定了down的大小比size的大小大1,这样中位数一定在down中
* 如果没有明确规定哪个堆中数据更多,而只是保证两者大小的差的绝对值不大于1,那么中位数出现在size更大的堆中,
* 虽然这样同样可以得出结论,但由于没有明确规定中位数到底出现在哪里,所以代码实现时判断条件要更多一些
*/
if (down.size() > up.size() + 1) up.push(down.top()), down.pop();
/**
* 按照理论,需要保证大根堆down的个数使用比小根堆的个数大一
* 即合法状态为up.size() = down.size() - 1,所以当up.size() >= down.size()时就需要调整,可这里没有=为什么也能ac
* 原因在于题目中规定了奇数个数时才会选择中位数,此时up和down中总数为奇数,两者size不可能相等,所以>时才调整也能保证到奇数个时能够得到正确结果
*/
if (up.size() >= down.size()) down.push(up.top()), up.pop();
if (i % 2 != 0)
{
cout << down.top() << ' ';
if (++ cnt % 10 == 0) cout << endl;
}
}
if (cnt % 10) cout << endl; // 解决最后一行数据不够10个,上方未输出空行,会导致下面组的格式出问题
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理