栈和队列——4.前k个高频元素

力扣题目链接

给定一个非空的整数数组,返回其中出现频率前 k 高的元素。

示例:

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

题干很简单,就是对数组中的元素进行频次计算,找到频次最多的前k和元素。那么首先就要统计元素出现的频率,然后对其进行排序,返回前k个值。

统计频率很简单,用个map映射一下,出现一次次数加一就行了,那么如何进行排序呢?《代码随想录》中介绍了一种利用堆的方法。什么是堆呢,堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。 如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆。因为小顶堆可以每次将最小的元素弹出,所以这里选择小顶堆。关于二叉树的内容在下一章会介绍到。

根据示例中[1,1,1,2,2,3],‘1’有3次,‘2’有2次,‘3’有1次,那我小顶堆长什么样子呢?

这里为元素值,而不是元素的次数,由于是小顶堆,所以这三个中3是对应次数最小的,即以1——2——3的顺序排列,因为k=2,那我在堆中再进行一次弹出,弹出了3,留下了[1,2]。

完整代码如下:

import heapq
class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        #要统计元素出现频率
        map_ = {} #nums[i]:对应出现的次数
        for i in range(len(nums)):
            map_[nums[i]] = map_.get(nums[i], 0) + 1
        
        #对频率排序
        #定义一个小顶堆,大小为k
        pri_que = [] #小顶堆
        
        #用固定大小为k的小顶堆,扫描所有频率的数值
        for key, freq in map_.items():
            heapq.heappush(pri_que, (freq, key))
            if len(pri_que) > k: #如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
                heapq.heappop(pri_que)
        
        #找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
        result = [0] * k
        for i in range(k-1, -1, -1):
            result[i] = heapq.heappop(pri_que)[1]
        return result

首先,导入堆的第三方库heapq。第一部分,计算元素频率。定义一个空的map,map_[nums[i]] = map_.get(nums[i], 0) + 1是简化了代码的过程,完整是如果nums[i]不在map中,那就将其加入map,代码map_.get(nums[i], 0);如果nums[i]在map中,那就将其次数加1.

第二部分,定义一个小顶堆,开始扫描map,利用heapq.heappush自动开始建立小顶堆,该函数的()中,第一个为顶堆表示pri_que,第二个为(freq, key)代表先根据freq来排序的。如果两个元素的频率相同,则会根据key值来决定它们在堆中的顺序。

然后,只保留前k个数,如果堆的长度大于k,就heappop弹出最小的值。

定义一个大小为k的空数组,因为堆每次弹出的是最小的,但要求的结果数组是由大到小排列的,所以我们要从数组最后开始填充for i in range(k-1, -1, -1),从k-1索引(即数组最后)到能取到的0,每次以-1的步长。 result[i] = heapq.heappop(pri_que)[1],result[i]等于每次堆中弹出的值的[1]索引,即上面(freq, key)中的key。最后完成结果数组。

那我不用这个小顶堆行么,map不是自带排序么,我直接排序一下,找到前k个值不行么?也可以,利用map的排序,完整代码如下:

from typing import List

class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        # 统计每个元素出现的频率
        map_ = {}  # 元素: 出现次数
        for num in nums:
            map_[num] = map_.get(num, 0) + 1
        
        # 按照出现频率排序,得到频率最高的前 k 个元素
        sorted_items = sorted(map_.items(), key=lambda x: x[1], reverse=True)
        
        # 取出前 k 个元素的键
        result = [item[0] for item in sorted_items[:k]]
        
        return result

统计元素频率是一样的。sorted_items = sorted(map_.items(), key=lambda x: x[1], reverse=True)中利用sort进行排序,定义选择排序的函数key=lambda x: x[1],以x[1]即(key,freq)中的freq为对象进行排序。

result = [item[0] for item in sorted_items[:k]]代码是简化后的,但也就是定义空数组,然后将排序后数组中的[:k]部分取出来的item中的第一个数item[0](即key)取出来填入result。简化的代码写不出来就安安心心的拆分开来写完整一点。

从代码简洁度和理解来讲,map直接排序好像更简单一点,那这两种方法的区别在哪里呢?在于k的大小,对于小k的情况,小顶堆方法可能更高效,而对于较大k,排序方法可能更简单和直接。

  • 24
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值