第6章|二分、堆与优先队列
Unit 08 · LeetCode #347
先把重复数据压缩成频率候选,
再在候选之间维护 Top-K。
原数组中的每次出现并不是独立候选。第一步用哈希表将数组压缩为 value → frequency;第二步才按照频率选择最大的K个不同元素。
题目要求
给定整数数组 nums 和整数 k,返回出现频率最高的 k 个元素。答案可以按任意顺序返回。
题目保证答案唯一,并要求算法时间复杂度优于 O(n log n)。
约束
1 ≤ nums.length ≤ 10⁵;-10⁴ ≤ nums[i] ≤ 10⁴;1 ≤ k ≤ 不同元素数量;- 答案集合保证唯一。
官方示例
输出:[1,2]
输出:[1]
输出:[1,2]
本页两种方法
通用堆方案:计数后维护大小K的最小堆,时间 O(n+m log k)、空间 O(m+k)。
主提交方案:按频率建桶并从高频向低频扫描,时间和空间均为 O(n)。其中 m 是不同元素数。
候选单位从“数组中的每个位置”变成“每个不同元素及其频率”。同一值只产生一个候选。
堆中保存 (frequency,value)。堆的优先级由频率决定,返回结果则提取value。
频率最多为n,可建立 buckets[freq] 保存恰好出现freq次的所有元素。
题目返回的是K个元素,而不是第K大的单个频率。达到K个结果后即可停止。
大小K最小堆
频率桶 buckets[freq]
同一数值的多次出现应先聚合为一个频率候选。
| 频率排名 | 元素 | 频率 | Top-K |
|---|
计数与候选更新记录
堆方法不变量
处理完每个频率候选后,大小K最小堆保存当前频率最高的K个元素;堆顶是其中最低频边界。
桶方法为什么能线性
频率是 1…n 的整数。无需比较排序,直接按频率作为数组下标,再从n向1扫描。
完整排序比较所有候选;最小堆只维护前K频率边界;桶排序利用频率是有限整数这一额外结构,避免候选间比较。
必须先完成候选压缩,再选择优先级;否则容易把数值大小、出现位置或重复副本误当作排名标准。
示例中数值3最大,但只出现1次;高频元素是1和2。
返回要求是不同元素。三个1应压缩成候选 (3,1)。
频率上限由原数组长度n决定,而不是不同元素数量m。
边界之外可以存在相同频率;只要不存在导致多个不同Top-K集合的边界歧义,题目就保证唯一答案。
frequency={1:1}
返回[1]
所有候选都返回
顺序任意
频率逻辑不变
value → count,后续选择只遍历不同元素。n 决定。(value,count) 应进入 buckets[count]。n 向 1 扫描,而不是从低频向高频。k 个元素时立即返回,低频桶不会再改变答案集合。迁移题:LeetCode #973 最接近原点的 K 个点
候选从“不同元素”变成“二维点”,优先级从频率变成平方距离。保留方向由最大优先级反转为最小优先级,因此边界堆的方向也必须反转。
官方原题
给定二维点数组 points,其中 points[i]=[xᵢ,yᵢ],返回距离原点 (0,0) 最近的 k 个点。答案顺序任意,除顺序外答案保证唯一。
- 欧氏距离为
√(x²+y²);比较时可直接使用平方距离。 1 ≤ k ≤ points.length ≤ 10⁴。-10⁴ ≤ xᵢ,yᵢ ≤ 10⁴。
官方示例
输出:[[-2,2]]
输出:[[3,3],[-2,4]]
反向顺序同样有效
#347|保留最大频率
候选:不同元素
优先级:frequency
保留:最大的 K 个优先级
边界:Top-K 中最低频
结构:大小 K 最小堆
#973|保留最小距离
候选:二维点
优先级:x²+y²
保留:最小的 K 个优先级
边界:K 个最近点中最远者
结构:大小 K 最大堆;Python 用负距离模拟
按依赖顺序组装 #973
官方样例轨迹
负距离堆视图
最小堆根保存最小的负距离,也就是绝对距离最大的保留候选。
独立重写
不提供占位符。请独立实现大小 K 的最远边界堆。
第6章 Unit 08 Skill卡
前K高频元素包含两个独立设计:用哈希表决定候选是什么,用堆或频率桶决定如何选择Top-K。数据的优先级若是有限整数,还可以用桶下标替代比较排序。