订阅更新

按频率表求中位数:用累计频数定位

SQL 实战 123 题 · 第 20 题 困难 约 2 分钟读完

LeetCode 571 · 中位数、累计求和、频率展开

本文目录
  1. 面试怎么回答
  2. 题意
  3. 表结构
  4. 思路
  5. 参考解法
  6. 易错点

面试怎么回答

按数值排序算累计频数,每个数值占据(前缀和 - 频数, 前缀和] 这一段位置。总数的一半落在哪一段,那个数值就是中位数;总数为偶数且一半正好在分界上时会命中两个数值,取平均。整个过程不需要把频率表展开。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

题意

表里存的是「数值 + 出现次数」,而不是展开后的明细。求所有数字(按次数展开后)的中位数。

表结构

  • Numbers:Number(主键)、Frequency

思路

不用真的展开。按数值排序算累计频数:某个数值覆盖的位置区间是(之前的累计数, 之前的累计数 + 自身频数]。中位数位置是总数的一半附近,只要某个数值的区间覆盖了总数/2,它就是中位数(或中位数之一),对命中的数值取平均即可。

参考解法

写法 1:窗口累计频数,区间覆盖总数一半的数值就是中位数

select 
    avg(cast(number as float)) median
from 
    (
        select 
            Number,
            Frequency,
            sum(Frequency) over(order by Number) - Frequency prev_sum,
            sum(Frequency) over(order by Number) curr_sum
        from Numbers
    ) t1,
    (
        select 
            sum(Frequency) total_sum
        from Numbers
    ) t2
where 
    t1.prev_sum <= (cast(t2.total_sum as float) / 2) and 
    t1.curr_sum >= (cast(t2.total_sum as float) / 2)

写法 2:自连接计算累计频数,适合不支持窗口函数的环境

SELECT 
AVG(Number)median 
FROM
(SELECT n1.Number FROM Numbers n1 JOIN Numbers n2 ON n1.Number>=n2.Number 
 GROUP BY 
 n1.Number 
 HAVING 
 SUM(n2.Frequency)>=(SELECT SUM(Frequency) FROM Numbers)/2 
 AND 
 SUM(n2.Frequency)-AVG(n1.Frequency)<=(SELECT SUM(Frequency) FROM Numbers)/2
)s

易错点

  • 真的把频数展开成明细会让数据量爆炸。
  • MySQL 8.0.17 之前不支持 CAST(... AS FLOAT),可以改成乘以 1.0 或 CAST AS DECIMAL。
刷题进度保存在当前浏览器