题目链接:https://leetcode.cn/problems/top-k-frequent-elements/description/
我的思路
乍一看这题好像还挺简单的,找到前K个高频元素,那么很自然可以想到先统计每个元素的出现频率,然后再排序,输出前K个频率最高的即可。直接暴力求解:1. count = [[num, nums.count(num)] for num in set(nums)]
统计元素出现频率,和元素一起放到count列表中;2. count = sorted(count, key = lambda x: x[1], reverse = True)
按频次从大到小排序;3. return [x[0] for x in count[:k]]
return排序好的count数组的前k个元素即可。但显然这不是最优解,这样的算法时间复杂度为$O(N\log n)$,对于特别长的序列,耗时特别久,最终结果为1378ms,也是第一次见到这么久的解题过程。那么显然要想提高时间和内存表现的话,就必须采用其他的降低复杂度的算法。
纠正
这里用“顶堆法”,会很方便和快捷,大幅度提高时间效率。python中默认内置了堆结构,需要用 import heapq
来引入,堆结构能够保证堆顶始终为最小的元素(即heap[0]始终为最小的值,当作为数组(a, b)压入堆时,则以a的大小来进行评判,最小的a值会被放在heap[0]处),于是乎,我们只需要首先计算每个元素的出现频率,把他们放到frequencyMap这个字典中,接着定义一个minHeap列表作为堆:
- 首先先把k个元素压入堆中,直到满堆,判断条件为
len(minHeap) < k
,执行语句heapq.heappush(minHeap, (freq, num))
,其中(freq, num)
对应了frequencyMap中的items - 如果堆已经满了,那就开始判断新的元素和堆顶元素的出现频率情况,如果新元素的频率大于当前堆中最低的那个频率,那就要把这个元素进行替换(因为我们要找的是前k个频率最高的元素,因此元素出现频率越高,越应该入栈),执行
heapq.heappushpop(minHeap, (freq, num))
- 重复上述步骤,直到判断完所有frequencyMap中的元素,结束循环
- 因为我们在判断语句中限制了堆的长度一定刚好为k(题目中说了一定有解,所以不会出现堆不满的情况,只能是刚好或者总的元素个数大于k),只允许压入k个元素,之后执行的都是替换语句,并不会改变堆的长度,因此执行到最后,堆中就是我们要找的k个频率最高的元素,将这些元素输出即可,而且题目中说了无需理会输出的顺序,因此直接无脑输出就行
return [num for freq, num in minHeap]
反思
- Python内置堆heapq,接受元组输入,默认第一个元素为优先级,堆顶始终为最小值
初始化字典可以用字典的.setdefault(key, value)方法,如果此时字典中没有key,那么久初始化其为value值,如果已经有了,就返回当前key对应的值;但更优的做法是用.get方法,dict.get(key, 0)为获取字典中key对应的value值,如果不存在,则返回0,但不会直接修改原字典中的元素:
get
:只读取,不修改字典。setdefault
:在读取时还可能会写入字典(保证 key 存在)
- 列表的合并用extend方法,延长列表,而不是增加元素append,比如[1,2,3].extend([5,7])结果为[1,2,3,5,7],而append的结果为[1,2,3,[5,7]],换言之,extend方法只接受列表作为输入,append既可以接受列表也可以是单独的元素
结果
按照纠正的思路,算法复杂度为$O(N\log k)$,优于$O(N\log n)$,3ms 89.16%, 20.81MB 70.4%