LeetCode 703. 数据流中的第 K 大元素
题目描述


题意分析
初始数组和之后加入的元素共同组成数据流,每次加入后返回按从大到小排序的第
k项。相同数值每出现一次都占一个位置,因此需要保留重复元素。
解法:固定容量小根堆
核心思路
[!blue]
维护一个最多存放
k项的小根堆:数据不足k项时全部保留,否则只保留当前最大的k项。堆满后,堆顶是保留项中的最小值,恰好处于从大到小的第k个位置。加入新值时,堆未满就直接放入。堆已满时,把堆顶看作进入前
k项的门槛:新值更大,就删除旧堆顶并加入新值;新值不大于堆顶,则已有k项不小于它,保留原堆即可。新值等于堆顶时,保留哪一次出现都不影响排名对应的数值。这样维护后,堆始终保留所需的最大
k项。之后只会新增数据,第k大的门槛不会降低,因此已经拒绝或淘汰的较小项无需重新考虑。每次只读取堆顶作为答案,保留堆内数据供后续添加继续使用。
解题步骤
- 保存
k并创建空的小根堆,构造时依次复用add添加初始数组中的元素。add中先检查堆大小:少于k项时直接入堆。- 已有
k项时,只有新值大于堆顶才弹出堆顶并加入新值。- 返回当前堆顶。构造过程中可能还不足
k项,此时内部调用的返回值会被忽略;题目保证k <= nums.length + 1,所以外部第一次调用add后就已有足够的元素。
代码实现
class KthLargest {
private final int k;
private final PriorityQueue<Integer> heap;
public KthLargest(int k, int[] nums) {
this.k = k;
heap = new PriorityQueue<>();
for (int num : nums) {
add(num);
}
}
public int add(int val) {
// 未满先接纳,满后只有超过门槛的值才需替换
if (heap.size() < k) {
heap.offer(val);
} else if (val > heap.peek()) {
// 堆中只保留当前最大的 k 个数,堆顶就是第 k 大。
heap.poll();
heap.offer(val);
}
// 只查看门槛,不弹出仍需保留的第 k 大
return heap.peek();
}
}
import "container/heap"
type IntHeap struct {
data []int
}
func (h IntHeap) Len() int {
return len(h.data)
}
func (h IntHeap) Less(i int, j int) bool {
return h.data[i] < h.data[j]
}
func (h IntHeap) Swap(i int, j int) {
h.data[i], h.data[j] = h.data[j], h.data[i]
}
func (h *IntHeap) Push(x any) {
h.data = append(h.data, x.(int))
}
func (h *IntHeap) Pop() any {
val := h.data[len(h.data)-1]
h.data = h.data[:len(h.data)-1]
return val
}
type KthLargest struct {
k int
heap IntHeap
}
func Constructor(k int, nums []int) KthLargest {
kth := KthLargest{k: k, heap: IntHeap{}}
heap.Init(&kth.heap)
for _, num := range nums {
kth.Add(num)
}
return kth
}
func (this *KthLargest) Add(val int) int {
// 未满先接纳,满后只有超过门槛的值才需替换
if this.heap.Len() < this.k {
heap.Push(&this.heap, val)
} else if val > this.heap.data[0] {
// 堆中只保留当前最大的 k 个数,堆顶就是第 k 大。
heap.Pop(&this.heap)
heap.Push(&this.heap, val)
}
// 只查看门槛,不弹出仍需保留的第 k 大
return this.heap.data[0]
}
复杂度分析
- 时间复杂度:构造 $O(n\log(k+1))$,其中 $n$ 为初始数组长度;单次添加均摊 $O(\log(k+1))$,包含底层数组偶发扩容的成本。满堆时若新值未超过堆顶,只需 $O(1)$。
- 空间复杂度:$O(k)$,堆不超过
k项。
关键点总结
[!green]
- 第
k大转化为最大k项中的最小值。- 满堆时拒绝较小值不会影响未来结果。
k = 1时同一逻辑只保留最大值;初始数组为空时,题目约束保证k = 1,第一次加入后即可正常返回。
易错点总结
[!yellow]
- 使用大根堆返回的会是最大值,而不是保留集合的门槛。
- 不比较就先弹旧堆顶再加入较小值,会丢失本应保留的数。
- 查看答案时弹出,破坏后续维护。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 215. 数组中的第K个最大元素 | 中等 | 原题一次性求数组第k大,本题持续加入数据,需要维持大小k的小顶堆。 |
| 295. 数据流的中位数 | 困难 | 同样动态查询排名边界,中位数位置随总数变化,需要维护两半而不是固定k项。 |
| 347. 前 K 个高频元素 | 中等 | 用大小受限的堆保留排名靠前的候选;本题支持持续插入时维护第 k 大,该题以元素频次为排序依据。 |
| 692. 前K个高频单词 | 中等 | 用大小受限的堆保留排名靠前的候选;本题支持持续插入时维护第 k 大,该题以单词频次和字典序联合排序。 |
| 973. 最接近原点的 K 个点 | 中等 | 用大小受限的堆保留排名靠前的候选;本题支持持续插入时维护第 k 大,该题以到原点的平方距离为排序依据。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!