程序员老鬼

某大厂员工爆料:强烈建议集团对P9及以上的领导进行智力筛查、记忆力筛查、老年痴呆筛查,特别一心想要在公司退休的人。

现在有些大厂高管给人的感觉,真不是“管理经验丰富”,员工都忍不住开喷,说建议给P9以上做个智力、记忆力筛查,重点看看那种年纪上来了、还死守位置等退休的领导。话糙,但你别说,评论区一堆人秒懂。

Image

有人说这帮人开会永远三件套:重复老话、听不进人话、最后拍个离地的板。还有人更损,说他们不是没经验,是经验太旧,旧到还活在上个版本,公司早更新了,他们脑子还没打补丁。

最烦的其实不是年纪大,是明明跟不上,还特别爱指挥,张嘴战略,落地全靠下面的人擦屁股。年轻人加班改方案,他们一句“我记得以前不是这么做的”,HR看完估计都头皮发麻。

面试题:数据流的中位数

堆里这玩意,看着像模板题,真放到线上就不是那么回事了。

比如实时取监控指标中位数、直播间礼物价格中位数、撮合系统滑动统计,数据不是一次性给你一坨数组让你排完序再算,而是一条一条往里灌。你每来一个数都 sort 一遍,代码是能写,性能也是真的难看。

这题我一般不会先想“怎么求中位数”,而是先想两件事: 第一,数据流是持续进入的,之前排好的结果最好别动太多。 第二,中位数只关心中间,不关心两头那些离谱的大数小数。

所以这题的手感,基本就是两个堆。

一个大顶堆,装较小的一半数据。 一个小顶堆,装较大的一半数据。

这样一来:

  • 大顶堆堆顶,就是“小的一半里最大的那个”
  • 小顶堆堆顶,就是“大的一半里最小的那个”

中位数刚好就在这两个位置附近。

再加一条规矩,整个结构就稳了:两个堆的元素个数差不能超过 1,而且让大顶堆的元素个数始终大于等于小顶堆。

那中位数怎么取就很顺了:

  • 总数是奇数,中位数就是大顶堆堆顶
  • 总数是偶数,中位数就是两个堆顶平均值

代码直接看,别整太虚的。

import java.util.Collections;
import java.util.PriorityQueue;

publicclassMedianFinder{

// 存较小的一半,堆顶最大
privatefinal PriorityQueue<Integer> left =
new PriorityQueue<>(Collections.reverseOrder());

// 存较大的一半,堆顶最小
privatefinal PriorityQueue<Integer> right =
new PriorityQueue<>();

publicvoidaddNum(int num){
// 先放左边,再把左边最大的挪到右边
        left.offer(num);
        right.offer(left.poll());

// 保证 left 的数量 >= right
if (left.size() < right.size()) {
            left.offer(right.poll());
        }
    }

publicdoublefindMedian(){
if (left.isEmpty()) {
return0.0;
        }

if (left.size() > right.size()) {
return left.peek();
        }

return (left.peek() + right.peek()) / 2.0;
    }

publicstaticvoidmain(String[] args){
        MedianFinder finder = new MedianFinder();
        finder.addNum(5);
        finder.addNum(2);
        finder.addNum(8);
        finder.addNum(3);

        System.out.println(finder.findMedian()); // 4.0
    }
}

这段写法我比较喜欢,原因不是“短”,而是稳定。 很多人会写一堆 if (num <= left.peek()) 这种分支,逻辑也对,但边界容易绕晕。尤其是空堆、重复值、负数一起进来的时候,调半天。

上面这个写法的思路很直接: 不管新来的数是谁,先丢左边; 再把左边最大的拨到右边; 最后如果右边比左边多了,就再拨回来。

这样天然保证:

  • 左边所有元素 <= 右边所有元素
  • 两边数量平衡

时间复杂度也合适:

  • addNum() 是 O(log n)
  • findMedian() 是 O(1)

这题真正该记住的,不是“双堆”这三个字,而是这种拆问题的习惯: 你不是在维护完整有序数组,你只是在维护“中间那条线”两边的平衡。

题不难,难的是第一次别往 ArrayList + 插入排序 那个方向拐。那种代码一开始能跑,数据一上来就开始发热了。