前 K 个高频元素

标签: 数组 哈希表 分治 桶排序 计数 快速选择 排序 堆(优先队列)

难度: Medium

给定一个整数数组 nums 和一个整数 k ,请返回其中出现频率前 k 高的元素。可以按 任意顺序 返回答案。

示例 1:

输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

示例 2:

输入: nums = [1], k = 1
输出: [1]

提示:

  • 1 <= nums.length <= 105
  • k 的取值范围是 [1, 数组中不相同的元素的个数]
  • 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的

进阶:所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n 是数组大小。

注意:本题与主站 347 题相同:https://leetcode-cn.com/problems/top-k-frequent-elements/

Submission

运行时间: 28 ms

内存: 19.3 MB

class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        cnter = self.freq_cnter(nums)
        sorted_que = []
        for n, freq in cnter.items():
            heappush(sorted_que, (freq, n))
            if len(sorted_que) > k:
                heappop(sorted_que)
        ans = []
        for el in sorted_que:
            ans.append(el[1])
        return ans

    def freq_cnter(self, nums):
        cnter = collections.defaultdict(int)
        for n in nums:
            cnter[n] += 1
        return cnter

Explain

该题解的方法是使用哈希表和最小堆。首先,使用哈希表来统计每个元素的频率。然后,使用最小堆来维护当前频率最高的前k个元素。遍历哈希表中的每个元素及其频率,将其推入堆中。如果堆的大小超过k,就弹出堆顶元素,保证堆的大小始终为k。最后,从堆中取出所有元素,这些就是出现频率最高的前k个元素。

时间复杂度: O(n log k)

空间复杂度: O(u + k)

class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        cnter = self.freq_cnter(nums) # 创建并填充哈希表以统计频率
        sorted_que = [] # 初始化最小堆
        for n, freq in cnter.items(): # 遍历哈希表
            heappush(sorted_que, (freq, n)) # 将元素频率和元素本身推入堆
            if len(sorted_que) > k: # 如果堆的大小超过k
                heappop(sorted_que) # 弹出堆顶元素
        ans = [] # 存储结果
        for el in sorted_que: # 从堆中取出所有元素
            ans.append(el[1]) # 将元素添加到结果列表
        return ans

    def freq_cnter(self, nums):
        cnter = collections.defaultdict(int) # 使用defaultdict避免键不存在的错误
        for n in nums: # 遍历数组以填充字典
            cnter[n] += 1 # 增加每个元素的计数
        return cnter

Explore

使用最小堆而不是最大堆有助于有效地管理前k个最高频的元素。在这种方法中,堆的大小固定为k,并且堆顶元素是堆中频率最小的元素。当新的元素频率比堆顶元素的频率大时,将堆顶元素替换出来,新元素入堆,这样可以保证堆中始终存储的是最高的k个频率的元素。如果使用最大堆,则需要维护所有元素的堆,并且频繁地进行插入和删除操作,这在时间复杂度上通常不如使用固定大小的最小堆效率高。

在使用最小堆处理时,堆的大小是固定为k。当堆中的元素数量超过k时,意味着新加入的元素频率高于堆顶的元素(频率最低的元素)。因此,移除堆顶元素,然后将新元素添加进堆中,可以确保堆中始终保留的是频率最高的k个元素。这种方法通过始终淘汰频率最低的元素,保证了在任何时候堆内都是目前遇到的最高频的k个元素。

在最小堆中,元素是按照频率来排序的。堆中每个元素是一个二元组,其中第一个元素是频率,第二个元素是实际的数组值。当两个元素频率相同时,堆会根据元素值(二元组的第二个元素)来进一步排序,以保持一致的处理逻辑。这意味着如果两个元素频率相同,则它们在堆中的顺序将由它们的值决定。