前言
题单LeetCode 热题 100的堆部分,给出解析。
215. Kth Largest Element in an Array
题目描述
Given an integer array nums
and an integer k
, return the k^th
largest element in the array.
Note that it is the k^th
largest element in the sorted order, not the k^th
distinct element.
Can you solve it without sorting?
Example 1:
1 | Input: nums = [3,2,1,5,6,4], k = 2 |
Example 2:
1 | Input: nums = [3,2,3,1,2,4,5,5,6], k = 4 |
Constraints:
1 <= k <= nums.length <= 10^5
-10^4 <= nums[i] <= 10^4
解题方法
堆排序
要在一个整数数组 nums
中找到第 k
大的元素,且要求不能通过排序的方式来解决,这可以通过使用堆数据结构来实现。
我们可以使用最小堆(Min-Heap)来解决这个问题。具体步骤如下:
- 创建一个大小为
k
的最小堆。 - 遍历数组中的元素:
- 如果堆的大小小于
k
,将元素加入堆中。 - 如果堆的大小已经是
k
,检查当前元素是否大于堆顶元素(即堆中最小的元素)。如果大于堆顶元素,则将堆顶元素弹出,并将当前元素加入堆中。
- 如果堆的大小小于
- 遍历结束后,堆顶元素即为第
k
大的元素。
快速选择
快速选择算法是基于快速排序 (Quick Sort) 算法的思想发展而来的。它使用了分治 (Divide and Conquer) 的策略,通过一个选定的「基准值」(pivot) 将数组分为两部分,一部分包含所有小于基准值的元素,另一部分包含所有大于基准值的元素。与快速排序不同的是,快速选择不需要完全排序整个数组,而是根据需要找的是第 k 小还是第 k 大的元素,只处理包含目标元素的那部分数组。
快速选择算法的基本执行步骤如下:
- 从数组中选择一个元素作为基准值 (pivot)。
- 使用该基准值对数组进行划分,将所有小于基准值的元素放在它的左边,大于基准值的放在右边。
- 判断基准值的位置与 k 的大小:
- 如果基准值正好是第 k 个元素,直接返回该基准值。
- 如果基准值的位置大于 k,在左边的子数组中,继续进行快速选择。
- 如果基准值的位置小于 k,转到右边的子数组中,继续寻找第 k 大的元素。
- 重复以上步骤,直到找到第 k 个元素。
代码实现
堆排序
1 | import heapq |
快速选择
1 | class Solution: |
复杂度分析
方法 | 时间复杂度 | 空间复杂度 |
---|---|---|
堆排序 | $O(n)$ | $O(n)$ |
快速选择 | $O(n)$ | $O(logn)$ |
347. Top K Frequent Elements
题目描述
Given an integer array nums
and an integer k
, return the k
most frequent elements. You may return the answer in any order .
Example 1:
1 | Input: nums = [1,1,1,2,2,3], k = 2 |
Example 2:
1 | Input: nums = [1], k = 1 |
Constraints:
1 <= nums.length <= 10^5
-10^4 <= nums[i] <= 10^4
k
is in the range[1, the number of unique elements in the array]
.- It is guaranteed that the answer is unique .
Follow up: Your algorithm’s time complexity must be better than O(n log n)
, where n is the array’s size.
解题方法
要找到给定整数数组 nums
中的 k
个最频繁的元素,我们可以使用以下步骤:
- 计数元素频率:使用哈希表(或字典)来计算每个元素出现的频率。
- 构建最小堆:使用最小堆来保持
k
个频率最高的元素。最小堆可以确保我们在添加新元素时,能够轻松移除频率最低的元素。 - 返回结果:从堆中提取出
k
个元素。
代码实现
1 | from collections import Counter |
复杂度分析
时间复杂度:$O(nlogn)$
空间复杂度:$O(n)$
295. Find Median from Data Stream
题目描述
The median is the middle value in an ordered integer list. If the size of the list is even, there is no middle value, and the median is the mean of the two middle values.
- For example, for
arr = [2,3,4]
, the median is3
. - For example, for
arr = [2,3]
, the median is(2 + 3) / 2 = 2.5
.
Implement the MedianFinder class:
MedianFinder()
initializes theMedianFinder
object.void addNum(int num)
adds the integernum
from the data stream to the data structure.double findMedian()
returns the median of all elements so far. Answers within10^-5
of the actual answer will be accepted.
Example 1:
1 | Input |
Constraints:
-10^5 <= num <= 10^5
- There will be at least one element in the data structure before calling
findMedian
. - At most
5 * 10^4
calls will be made toaddNum
andfindMedian
.
Follow up:
- If all integer numbers from the stream are in the range
[0, 100]
, how would you optimize your solution? - If
99%
of all integer numbers from the stream are in the range[0, 100]
, how would you optimize your solution?
解题方法
这个问题要求我们设计一个 MedianFinder
类,来支持添加数字并能够实时地返回数据流中所有数字的中位数。
解题思路:
维护数据结构:可以通过使用两种数据结构来解决这个问题,分别是最大堆(
max heap
)和最小堆(min heap
)。- 最大堆:用来存储较小的一半数字。
- 最小堆:用来存储较大的一半数字。
堆的作用:
- 由于堆是一个完全二叉树结构,可以高效地获取最大值(最大堆)和最小值(最小堆)。我们使用两个堆来保持数据的平衡。
- 如果总数为奇数,最大堆会比最小堆多一个元素,返回最大堆的根节点就是中位数。
- 如果总数为偶数,两个堆的大小相同,返回两个堆根节点的平均值。
算法步骤:
- 初始化两个堆:最大堆和最小堆。
- 添加数字:
- 先将数字添加到最大堆中(因为最大堆根节点是当前较小的一半数字)。
- 然后将最大堆的根节点移到最小堆,以保证两个堆的元素尽量平衡。
- 如果最小堆的大小大于最大堆,则将最小堆的根节点移回最大堆。
- 求中位数:
- 如果最大堆的大小比最小堆大,返回最大堆的根节点。
- 如果两个堆的大小相同,则返回两个堆根节点的平均值。
代码实现
1 | class MedianFinder: |
复杂度分析
每次 addNum
操作的时间复杂度是 $O(log n)$,因为每次加入元素后可能需要调整堆。
每次 findMedian
操作的时间复杂度是 $O(1)$,只需要查看堆的根节点。