【前后缀分解 对顶堆】高效求解子数组中位数:从P12570题解看算法优化

1. 从一道竞赛题说起:为什么动态中位数这么难?

大家好,我是老张,一个在算法和数据结构里摸爬滚打了十多年的老码农。今天想和大家聊聊一个听起来有点绕,但实际应用中又特别有用的东西:如何高效地求解滑动窗口或者子数组的中位数。这个话题的引子,是我最近在复盘UOI 2023的一道题目,P12570。这道题的核心,就是判断一个数组能否被分割成若干个奇数长度的子数组,并且这些子数组的中位数都相等。

乍一看,这题好像挺简单的,不就是分割数组然后算中位数吗?但如果你真这么想,那大概率要掉坑里了。因为数据规模是 n ≤ 2×10^5,这意味着你不可能用最笨的 O(n^2) 甚至 O(n^3) 的方法去枚举所有分割点,然后对每个子数组排序求中位数。那样做,程序跑完估计黄花菜都凉了。所以,这道题逼着我们必须去寻找更高效的算法。

这里就引出了我们今天要深入探讨的两个关键技术:前后缀分解对顶堆。它们俩组合起来,能让我们在近乎线性的时间复杂度内,优雅地解决这类动态中位数问题。我刚开始接触这类问题时,也觉得一头雾水,什么“对顶堆”听着就玄乎。但后来自己动手实现了几遍,踩过几个坑之后,发现它的思想其实非常巧妙,而且应用场景远比想象中广泛。比如,实时监控系统里统计最近一分钟请求的响应时间中位数,或者金融分析里计算滑动窗口内的价格中位数,本质上都是同一个问题。

那么,我们先来理解一下P12570这道题到底在问什么。题目给了一个长度为偶数 n 的数组 a,要求我们判断:能不能把它切成若干段,每一段的长度都是奇数,并且每一段的中位数都相同。注意,这里“中位数”的定义是针对奇数长度数组的:排序后正中间的那个数。比如 [4,2,5] 排序后是 [2,4,5],中位数就是 4

2. 破题关键:一个被忽略的强力性质

在动手写代码之前,我们得先进行“思维优化”。很多算法题,尤其是竞赛题,直接蛮干是行不通的,必须找到题目背后隐藏的数学性质或者规律。对于P12570,有一个非常关键的性质,我称之为“三段合一”性质。

性质:如果有三个连续的、奇数长度的子数组,它们的中位数相同,那么把这三个子数组合并成一个大的奇数长度子数组,这个新数组的中位数仍然不变。

为什么?我们来举个具体的例子。假设三个子数组长度分别是 3, 5, 7(都是奇数),中位数都是 K。那么,在第一个长度为3的数组里,有1个数小于等于 K,1个数大于等于 K(中位数本身算一个)。同理,第二个数组有2个小于等于,2个大于等于;第三个有3个小于等于,3个大于等于。合并之后,总长度是 3+5+7=15(奇数),其中小于等于 K 的数总共有 1+2+3 = 6 个,大于等于 K 的数也是 6 个。而 K 本身出现了3次。在一个排序后的15个数的序列里,第8个数((15+1)/2=8)是中位数。由于有6个数严格小于 K,加上3个 K 中的第一个,第7、8、9个数都是 K,所以中位数依然是 K

这个性质不是巧合,是严格成立的。它的威力在于,给了我们一个巨大的简化:我们只需要考虑将原数组分割成一段或者两段的情况就够了。为什么呢?想想看,如果你找到了一个分割方案,分成了超过两段(比如三段),那么根据上述性质,你可以把其中三段合并成一段,中位数不变,段数就减少了。一直合并下去,最终要么只剩一段(整个数组),要么只剩两段。题目又说了 n 是偶数,整个数组长度是偶数,不符合“奇数长度”的要求,所以整个数组自己作为一段是不行的。因此,最终的检查,就简化成了:是否存在一个分割点,将数组分成两个奇数长度的子数组,并且它们的中位数相等

这一步的思维跳跃非常重要,它直接把一个看起来要枚举指数级分割方案的问题,降维成了一个 O(n) 可检查的问题。我见过很多朋友卡在这道题,就是因为没有悟出这个性质,总想着去动态规划或者搜索所有分割方式,结果复杂度爆炸。所以,做题和做项目一样,有时候“想”比“写”更重要。

3. 核心技术一:对顶堆,动态中位数的“守护神”

好了,现在问题变成了:我们需要快速得到数组中任意一个奇数长度前缀和对应奇数长度后缀的中位数。具体来说,对于所有奇数 i(1, 3, 5, ...),我们需要计算 a[1..i] 的中位数(前缀中位数),以及 a[i+1..n] 的中位数(后缀中位数)。然后检查有没有某个 i 能使两者相等。

那么,如何快速计算一个动态增加元素的序列的中位数呢?这就是对顶堆大显身手的地方了。我把它比作一个天平和两个托盘。想象一下,我们要维护一个数据流,随时能知道当前所有数字的中位数。对顶堆用两个堆来实现:

  • 一个最大堆left
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值