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^4
  • 1 <= 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),findMedian O(1);n 为当前元素个数。
posted @ 2026-10-12 00:26  Phier  阅读(2)  评论(0)    收藏  举报