Leetcode 堆 1
题号直达: 215 数组中的第 K 个最大元素 | 347 前 K 个高频元素 | 295 数据流的中位数
LeetCode 215. 数组中的第 K 个最大元素 ↑
题目
给定整数数组 nums 和整数 k,返回数组中第 k 个最大的元素。注意这里找的是排序后第 k 大,不是第 k 个不同元素。
要求设计并实现时间复杂度为 O(n) 的算法。
测试样例
- 示例 1:
nums = [3,2,1,5,6,4],k = 2
输出:5 - 示例 2:
nums = [3,2,3,1,2,4,5,5,6],k = 4
输出:4
提示
1 <= k <= len(nums) <= 10^5-10^4 <= nums[i] <= 10^4
代码(Python)方法1:直接排序
先对数组升序排序,再返回下标 len(nums)-k 的元素。实现最直观,但排序时间为 O(n log n),通常不满足面试里这题对线性时间(或更优平均复杂度)的要求。
class Solution(object):
def findKthLargest(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: int
"""
nums.sort()
return nums[len(nums) - k]
思路解析(方法1)
- 核心:排序后第
k大就是倒数第k个。 - 优点:代码短、可读性高、不易出错。
- 局限:时间复杂度 O(n log n),在这题里常被认为不够“面试友好”。
代码(Python)方法2:手写大顶堆(站主自主完成)
先原地建大顶堆,再执行 k-1 次“堆顶与末尾交换 + 堆大小减一 + 下沉调整”,最终堆顶就是第 k 大元素。
class Solution(object):
def maxHeapify(self, a, i, heapSize):
l = i * 2 + 1
r = i * 2 + 2
largest = i
if l < heapSize and a[l] > a[largest]:
largest = l
if r < heapSize and a[r] > a[largest]:
largest = r
if largest != i:
a[i], a[largest] = a[largest], a[i]
self.maxHeapify(a, largest, heapSize)
def buildMaxHeap(self, a, heapSize):
for i in range(heapSize // 2 - 1, -1, -1):
self.maxHeapify(a, i, heapSize)
def findKthLargest(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: int
"""
heapSize = len(nums)
self.buildMaxHeap(nums, heapSize)
for i in range(len(nums) - 1, len(nums) - k, -1):
nums[0], nums[i] = nums[i], nums[0]
heapSize -= 1
self.maxHeapify(nums, 0, heapSize)
return nums[0]
方法2 讲解视频(讲解视频文件 0415_1.mp4)
思路解析(方法2)
- 建堆:从最后一个非叶子节点开始向前
heapify,得到大顶堆。 - 弹出最大值:每轮把堆顶(当前最大)交换到堆尾并缩小堆范围,重复
k-1轮后,堆顶就是第k大。 - 原地算法:在
nums上原地调整,不需要额外堆容器。 - 复杂度:建堆
O(n),每次下沉O(log n),共k-1次,整体O(n + k log n);空间近似O(1)(递归栈另计)。
LeetCode 347. 前 K 个高频元素 ↑
题目
给定整数数组 nums 和整数 k,返回其中出现频率前 k 高的元素。答案顺序可任意。
测试样例
- 示例 1:
nums = [1,1,1,2,2,3],k = 2
输出:[1,2] - 示例 2:
nums = [1],k = 1
输出:[1] - 示例 3:
nums = [1,2,1,2,1,2,3,1,3,2],k = 2
输出:[1,2]
提示
1 <= len(nums) <= 10^5-10^4 <= nums[i] <= 10^41 <= k <=数组中不同元素个数- 题目保证前
k个高频元素集合唯一
代码(Python)方法1:哈希表 + 排序
先用字典统计频次,再把 (频次, 元素) 配对后按频次排序,最后取后 k 个元素。
class Solution(object):
def topKFrequent(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: List[int]
"""
dic = {}
for i in range(len(nums)):
if nums[i] not in dic:
dic[nums[i]] = 1
else:
dic[nums[i]] += 1
key = list(dic.keys())
value = list(dic.values())
sorted_pairs = sorted(zip(value, key))
key = [n for _, n in sorted_pairs]
return key[len(key) - k : len(key)]
思路解析(方法1)
- 计数:哈希表
dic统计每个元素出现次数。 - 按频次排序:
sorted(zip(value, key))先按频次升序,再切片取后k个。 - 优点:实现直观,便于快速写出可用解。
- 局限:整体复杂度约 O(m log m)(
m为不同元素个数),面试常会追问堆或桶排序优化。
代码(Python)方法2:Counter + 小顶堆
先用 Counter 统计频次,再维护一个大小最多为 k 的小顶堆(元素为 (频率, 数值)):
- 堆未满:直接入堆;
- 堆已满:仅当当前频率
count大于堆顶最小频率时,用heapreplace替换。
遍历结束后,堆里剩下的就是前 k 个高频元素。
import heapq
from collections import Counter
class Solution(object):
def topKFrequent(self, nums, k):
occurrences = Counter(nums)
heap = []
for num, count in occurrences.items():
if len(heap) == k:
if count > heap[0][0]:
heapq.heapreplace(heap, (count, num))
else:
heapq.heappush(heap, (count, num))
return [num for count, num in heap]
思路解析(方法2)
- 堆含义:堆顶始终是当前候选集合中“频率最小”的元素,便于比较是否应该淘汰。
- 保留 top-k:遍历所有不同元素后,堆中恰好保留频率最高的
k个。 - 复杂度:设不同元素个数为
m,堆大小为k,时间约 O(m log k),空间 O(m + k)(Counter+ 堆)。
LeetCode 295. 数据流的中位数 ↑
题目
困难。 中位数是有序整数列表中间的数;若个数为偶数,则为中间两数的平均值。
实现 MedianFinder:
MedianFinder()初始化;addNum(num)从数据流加入整数;findMedian()返回当前所有元素的中位数(与真实值误差在10^-5内即可)。
测试样例(节选)
addNum(1),addNum(2),findMedian()→1.5;再addNum(3),findMedian()→2.0。
提示
-10^5 ≤ num ≤ 10^5- 首次调用
findMedian前数据结构中至少有一个元素 addNum与findMedian合计最多调用5 * 10^4次
代码(Python):双堆(小顶堆 + 大顶堆模拟)
queMin 存较小一半(用 heapq 存负数模拟大顶堆,堆顶为较小一半中的最大值);queMax 为小顶堆,存较大一半,堆顶为较大一半中的最小值。始终保持 len(queMin) == len(queMax) 或 len(queMin) == len(queMax) + 1。findMedian:总个数为偶数时取两堆顶平均;为奇数时中位数在元素较多的一侧,即 queMin 堆顶(取负还原)。
import heapq
class MedianFinder(object):
def __init__(self):
self.queMin = []
self.queMax = []
def addNum(self, num):
"""
:type num: int
:rtype: None
"""
if not self.queMin:
heapq.heappush(self.queMin, -num)
elif num <= -self.queMin[0]:
heapq.heappush(self.queMin, -num)
if len(self.queMin) - len(self.queMax) >= 2:
temp = -heapq.heappop(self.queMin)
heapq.heappush(self.queMax, temp)
elif num > -self.queMin[0]:
heapq.heappush(self.queMax, num)
if len(self.queMax) > len(self.queMin):
temp = heapq.heappop(self.queMax)
heapq.heappush(self.queMin, -temp)
def findMedian(self):
"""
:rtype: float
"""
if not self.queMin:
return 0.0
if (len(self.queMax) + len(self.queMin)) % 2 == 0:
return float(self.queMax[0] - self.queMin[0]) / 2
else:
return float(-self.queMin[0])
思路解析
- 不变式:
queMin存较小半区(用负数实现大顶堆),queMax存较大半区;两堆大小至多差 1,且较小半区元素个数不少于较大半区(奇数时多出的一个在中位数左侧堆顶)。 - 偶数长度:中位数为
(queMax[0] + (-queMin[0])) / 2,写成(queMax[0] - queMin[0]) / 2(因queMin存负值)。 - 空结构:题目保证
findMedian调用前已有数据;若queMin仍空,返回0.0仅为防御写法。 - 复杂度:每次
addNum堆操作 O(log n),findMedianO(1);n为当前元素个数。

浙公网安备 33010602011771号