堆与优先队列 (Heaps and Priority Queues)
堆与优先队列 (Heaps and Priority Queues)
堆是一种特殊的完全二叉树,常用来实现优先队列。堆具有以下特性:
- 完全二叉树结构
- 每个节点的值都大于等于(最大堆)或小于等于(最小堆)其子节点的值
基础概念
堆的结构
最大堆: 最小堆:
100 1
/ \ / \
19 36 4 2
/ \ / / \ /
17 3 25 17 5 3
数组表示
数组索引关系:
parent(i) = (i-1)/2
left_child(i) = 2i + 1
right_child(i) = 2i + 2
最大堆数组: [100, 19, 36, 17, 3, 25]
索引: 0 1 2 3 4 5
基本操作
- 上浮(Heapify Up)
插入50:
100 100
/ \ / \
19 36 → 50 36
/ \ / / \ /
17 3 25 17 3 25
/
50
数组: [100,19,36,17,3,25,50] → [100,50,36,17,3,25,19]
- 下沉(Heapify Down)
删除根节点:
100 50
/ \ / \
50 36 → 19 36
/ \ / / \ /
17 3 25 17 3 25
数组: [100,50,36,17,3,25] → [50,19,36,17,3,25]
经典问题
1. 数组中的第K个最大元素
LeetCode 215 - Kth Largest Element in an Array
使用最小堆维护K个最大元素:
输入: [3,2,1,5,6,4], k=2
堆的构建过程:
1. [3]
2. [2,3]
3. [1,2,3] → [2,3] (保持大小为2)
4. [2,3,5] → [3,5]
5. [3,5,6] → [5,6]
6. [4,5,6] → [5,6]
返回: 5
def findKthLargest(nums, k):
heap = []
for num in nums:
heapq.heappush(heap, num)
if len(heap) > k:
heapq.heappop(heap)
return heap[0]
2. 合并K个排序链表
LeetCode 23 - Merge k Sorted Lists
使用最小堆优化多路归并:
输入链表:
1->4->5
1->3->4
2->6
最小堆过程:
初始: [1,1,2]
步骤1: 1 [1,2,4]
步骤2: 1 [2,3,4]
步骤3: 2 [3,4,4]
步骤4: 3 [4,4,5]
...
输出: 1->1->2->3->4->4->5->6
def mergeKLists(lists):
heap = []
# 将所有链表的头节点入堆
for i, lst in enumerate(lists):
if lst:
heapq.heappush(heap, (lst.val, i, lst))
dummy = ListNode(0)
curr = dummy
while heap:
val, i, node = heapq.heappop(heap)
curr.next = node
curr = curr.next
if node.next:
heapq.heappush(heap, (node.next.val, i, node.next))
return dummy.next
3. 滑动窗口最大值
LeetCode 239 - Sliding Window Maximum
使用最大堆维护窗口元素:
输入: nums = [1,3,-1,-3,5,3,6,7], k = 3
窗口滑动过程:
[1,3,-1] → 3
[3,-1,-3] → 3
[-1,-3,5] → 5
[-3,5,3] → 5
[5,3,6] → 6
[3,6,7] → 7
输出: [3,3,5,5,6,7]
def maxSlidingWindow(nums, k):
result = []
heap = [] # (value, index)
for i, num in enumerate(nums):
# 添加当前元素
heapq.heappush(heap, (-num, i))
# 如果窗口已形成
if i >= k - 1:
# 移除窗口外的元素
while heap and heap[0][1] <= i - k:
heapq.heappop(heap)
result.append(-heap[0][0])
return result
4. 最小会议室数量
LeetCode 253 - Meeting Rooms II
使用最小堆跟踪会议结束时间:
输入: [[0,30],[5,10],[15,20]]
时间线处理:
0 : 开始会议1 [30]
5 : 开始会议2 [10,30]
10 : 结束会议2 [30]
15 : 开始会议3 [20,30]
20 : 结束会议3 [30]
30 : 结束会议1 []
返回: 2(最多同时需要2个会议室)
def minMeetingRooms(intervals):
if not intervals:
return 0
# 按开始时间排序
intervals.sort(key=lambda x: x[0])
# 最小堆存储结束时间
rooms = []
heapq.heappush(rooms, intervals[0][1])
for i in range(1, len(intervals)):
# 如果当前会议开始时间大于最早结束时间
if intervals[i][0] >= rooms[0]:
heapq.heappop(rooms)
heapq.heappush(rooms, intervals[i][1])
return len(rooms)
高级应用
1. 对顶堆
中位数维护:
最大堆(左半部分) 最小堆(右半部分)
[1,2,3] [4,5,6]
3 → 4
/ \ / \
1 2 5 6
2. 多路归并
K路归并示意:
[1,4,7]
[2,5,8] → MinHeap → [1,2,3,4,5,6,7,8,9]
[3,6,9]
3. 定时器实现
任务调度堆:
(2s)
/ \
(5s) (3s)
/ \
(7s) (6s)
常见技巧
- 使用堆优化排序
- 维护固定大小的堆
- 延迟删除
- 多路归并优化
- 对顶堆应用
相关题目
- LeetCode 295 - Find Median from Data Stream
- LeetCode 347 - Top K Frequent Elements
- LeetCode 451 - Sort Characters By Frequency
- LeetCode 621 - Task Scheduler
- LeetCode 973 - K Closest Points to Origin