优先队列(PriorityQueue)在基础的 Top-K 之外,还能解决数据流中位数、任务调度、图的最短路径优化等复杂问题。本篇聚焦堆的进阶应用与多堆协作模式。
一、Java PriorityQueue 速查
Mermaid · 渲染中(下方为源码)
graph LR A[优先队列] --> B[Top-K] A --> C[数据流中位数] A --> D[任务调度]
// 最小堆(默认)
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
// 最大堆
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder());
// 自定义
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]);
// 操作
pq.offer(x); // 入堆 O(log n)
pq.peek(); // 查看堆顶 O(1)
pq.poll(); // 弹出堆顶 O(log n)
pq.size(); // 大小 O(1)二、数据流中位数(LeetCode 295)
用两个堆:大顶堆存较小半,小顶堆存较大半。
class MedianFinder {
PriorityQueue<Integer> lo = new PriorityQueue<>(Collections.reverseOrder()); // 大顶堆
PriorityQueue<Integer> hi = new PriorityQueue<>(); // 小顶堆
void addNum(int num) {
lo.offer(num);
hi.offer(lo.poll());
if (hi.size() > lo.size()) lo.offer(hi.poll());
}
double findMedian() {
if (lo.size() > hi.size()) return lo.peek();
return (lo.peek() + hi.peek()) / 2.0;
}
}三、Top-K 问题
第 K 大元素(LeetCode 215)
// 方法一:最小堆 O(n log k)
int findKthLargest(int[] nums, int k) {
PriorityQueue<Integer> pq = new PriorityQueue<>();
for (int num : nums) {
pq.offer(num);
if (pq.size() > k) pq.poll();
}
return pq.peek();
}前 K 个高频元素(LeetCode 347)
int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> freq = new HashMap<>();
for (int n : nums) freq.merge(n, 1, Integer::sum);
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[1] - b[1]);
for (var entry : freq.entrySet()) {
pq.offer(new int[]{entry.getKey(), entry.getValue()});
if (pq.size() > k) pq.poll();
}
int[] result = new int[k];
for (int i = 0; i < k; i++) result[i] = pq.poll()[0];
return result;
}四、合并 K 个有序链表(LeetCode 23)
ListNode mergeKLists(ListNode[] lists) {
PriorityQueue<ListNode> pq = new PriorityQueue<>((a, b) -> a.val - b.val);
for (ListNode node : lists) {
if (node != null) pq.offer(node);
}
ListNode dummy = new ListNode(0), cur = dummy;
while (!pq.isEmpty()) {
ListNode node = pq.poll();
cur.next = node;
cur = cur.next;
if (node.next != null) pq.offer(node.next);
}
return dummy.next;
}五、任务调度(LeetCode 621)
// 贪心 + 堆:每轮选频率最高的任务
int leastInterval(char[] tasks, int n) {
int[] freq = new int[26];
for (char c : tasks) freq[c - 'A']++;
PriorityQueue<Integer> pq = new PriorityQueue<>(Collections.reverseOrder());
for (int f : freq) if (f > 0) pq.offer(f);
int time = 0;
while (!pq.isEmpty()) {
List<Integer> temp = new ArrayList<>();
for (int i = 0; i <= n; i++) {
if (!pq.isEmpty()) temp.add(pq.poll() - 1);
}
for (int f : temp) if (f > 0) pq.offer(f);
time += pq.isEmpty() ? temp.size() : n + 1;
}
return time;
}六、Dijkstra 中的堆优化
// 标准 Dijkstra
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[1] - b[1]); // {node, dist}
pq.offer(new int[]{src, 0});
while (!pq.isEmpty()) {
int[] cur = pq.poll();
int u = cur[0], d = cur[1];
if (d > dist[u]) continue; // 过期条目
for (int[] edge : adj[u]) {
int v = edge[0], w = edge[1];
if (dist[v] > d + w) {
dist[v] = d + w;
pq.offer(new int[]{v, dist[v]});
}
}
}七、面试要点
- 双堆中位数:大顶堆+小顶堆平衡
- Top-K 用最小堆:维护 k 个最大
- 多路归并用堆:K 个有序序列合并
- 懒删除:Dijkstra 中跳过过期条目
- LeetCode:215、295、347、23、621、703、973