一、思路分析
-
统计频率:
- 首先,需要知道数组中每个元素出现的频率。这里使用了 Python 标准库
collections中的Counter类来轻松实现这一功能。它会遍历数组nums,并自动统计每个元素出现的次数,将元素作为键,出现的频率作为值,存储在一个类似字典的结构中。
- 首先,需要知道数组中每个元素出现的频率。这里使用了 Python 标准库
-
利用最小堆筛选前 k 高频率元素:
- 接下来,要找出出现频率前
k高的元素。这里采用了最小堆(通过heapq模块实现)的数据结构。最小堆的特性是堆顶元素始终是堆中最小的元素。 - 遍历由
Counter统计得到的每个元素及其频率的键值对。对于每个元素,将其频率和元素值作为一个元组(freq, num)压入最小堆min_heap中。 - 在压入元素的过程中,需要检查堆的大小。如果堆的大小超过了
k,就将堆顶元素(即当前堆中频率最小的元素)弹出。这样,在遍历完所有元素后,最小堆中就保留了出现频率前k高的元素(虽然此时堆顶是这k个元素中频率相对最小的,但整体堆中存储的就是前k高频率的元素)。
- 接下来,要找出出现频率前
-
排序与输出:
- 从最小堆中提取出这
k个元素后,得到的列表top_k中的元素顺序是按照在堆中的顺序排列的,而这个顺序可能不是按照元素值的升序排列。所以,需要进一步对top_k按照元素值进行排序,这里使用了sort方法,并通过key参数指定按照元组中的第二个元素(即元素值)进行排序。 - 最后,将排序后的元素转换为字符串形式,并使用逗号连接起来,得到符合题目要求的返回结果。
- 从最小堆中提取出这
二、图解(示例以 nums = [4, 4, 4, 2, 2, 2, 3, 3, 1], k = 2 为例)
1. 统计频率阶段:
| 元素 | 频率 |
|---|---|
| 1 | 1 |
| 2 | 3 |
| 3 | 2 |
| 4 | 3 |
此时通过 Counter 得到了每个元素及其对应的频率。
2. 利用最小堆筛选阶段:
-
开始遍历上述的键值对并压入最小堆。
-
首先压入
(1, 1),此时堆中只有一个元素。 -
接着压入
(3, 2),堆会根据频率进行调整,此时堆顶是(1, 1)(因为 1 的频率最小)。 -
再压入
(2, 3),堆再次调整,堆顶依然是(1, 1)。 -
压入
(3, 4),堆调整后,堆顶还是(1, 1)。 -
此时堆的大小超过了
k = 2,所以弹出堆顶元素(1, 1)。 -
继续遍历完剩下的元素,最终堆中保留的是
(2, 3)和(3, 4)(这里只是示意,实际顺序由堆的调整规则确定,但肯定是频率相对较高的两个元素对应的元组)。
-
3. 排序与输出阶段:
- 从堆中取出元素得到
top_k = [(2, 3), (3, 4)]。 - 按照元素值排序后变为
[(3, 4), (2, 3)]。 - 最后转换为字符串并连接得到
"2,4"。
三、代码详解
收起
python
复制
from collections import Counter
import heapq
def solution(nums, k):
# 统计每个元素的频率
frequency = Counter(nums)
# 使用最小堆存储频率前 k 高的元素
min_heap = []
for num, freq in frequency.items():
heapq.heappush(min_heap, (freq, num))
if len(min_heap) > k:
heapq.heappop(min_heap)
# 提取堆中的元素,按频率和元素值排序
top_k = [heapq.heappop(min_heap) for _ in range(len(min_heap))]
# 按值排序
top_k.sort(key=lambda x: x[1])
# 格式化输出
result = ','.join(str(num) for _, num in top_k)
return result
-
frequency = Counter(nums):- 这行代码利用
Counter类统计数组nums中每个元素的出现频率。例如,如果nums = [1, 1, 2],那么frequency将会是一个类似字典的对象,其中{1: 2, 2: 1},表示元素 1 出现了 2 次,元素 2 出现了 1 次。
- 这行代码利用
-
min_heap = []:- 创建一个空的列表
min_heap,用于后续构建最小堆。
- 创建一个空的列表
-
for num, freq in frequency.items():...:-
遍历由
Counter统计得到的每个元素及其频率的键值对。对于每一对(num, freq):heapq.heappush(min_heap, (freq, num)):将元素的频率和元素值组成的元组(freq, num)压入最小堆min_heap中。堆会根据元组的第一个元素(即频率)自动调整结构,使得堆顶元素始终是频率最小的那个元组。if len(min_heap) > k::在每次压入元素后,检查堆的大小。如果堆的大小超过了指定的k值,就执行heapq.heappop(min_heap),弹出堆顶元素(即当前堆中频率最小的元素对应的元组),从而保证堆中始终保留的是出现频率前k高的元素对应的元组。
-
-
top_k = [heapq.heappop(min_heap) for _ in range(len(min_heap))]:- 通过列表推导式,依次从最小堆中弹出所有元素,并将它们存储在列表
top_k中。此时top_k中的元素顺序是按照在堆中的弹出顺序排列的,不一定是按照元素值的升序排列。
- 通过列表推导式,依次从最小堆中弹出所有元素,并将它们存储在列表
-
top_k.sort(key=lambda x: x[1]):- 使用
sort方法对top_k列表进行排序。通过指定key=lambda x: x[1],表示按照元组中的第二个元素(即元素值)进行排序,使得top_k中的元素按照元素值的升序排列。
- 使用
-
result = ','.join(str(num) for _, num in top_k):- 首先通过列表推导式
(str(num) for _, num in top_k)将top_k中每个元组的第二个元素(即元素值)转换为字符串形式。然后使用','.join()方法将这些字符串用逗号连接起来,得到最终符合题目要求的返回结果字符串。
- 首先通过列表推导式
四、知识总结
新知识点梳理与分析
-
Counter 类:
- 来自于
collections库,它提供了一种方便快捷的方式来统计可迭代对象(如列表、字符串等)中每个元素出现的次数。它返回一个类似字典的对象,其中键是元素,值是该元素出现的频率。对于处理需要统计元素出现频率的问题,使用Counter可以大大简化代码逻辑。例如,在本题中,如果手动去统计数组中每个元素的频率,需要编写较多的循环和判断逻辑,而Counter一行代码就搞定了。
- 来自于
-
heapq 模块与最小堆:
heapq是 Python 标准库中用于实现堆数据结构的模块。堆是一种特殊的树形数据结构,分为最小堆和最大堆。在本题中使用的是最小堆,其特点是堆顶元素始终是堆中最小的元素。通过heapq模块的heappush和heappop操作,可以方便地向堆中插入元素和从堆中弹出元素,并且堆会自动根据元素的大小(在本题中是根据元素频率)进行调整,保持堆的特性。利用最小堆的这种特性,能够高效地筛选出出现频率前k高的元素。与其他排序算法(如排序后再取前k个元素的方法)相比,在时间复杂度上有优势,尤其是当k相对于数组大小n较小时。
理解与学习建议
-
理解:
- 本题的核心在于巧妙地结合了
Counter统计频率和heapq构建最小堆这两个工具来解决找出前k高频率元素的问题。Counter让我们快速得到每个元素的频率信息,而最小堆则帮助我们在不需要对整个数组进行完全排序的情况下,高效地筛选出频率较高的元素。这种分阶段处理问题的思路在很多算法问题中都很常见,先对数据进行某种预处理(如本题的统计频率),然后再利用合适的数据结构(如本题的最小堆)进行进一步的筛选或处理。
- 本题的核心在于巧妙地结合了
-
学习建议:
- 对于刚入门的同学来说,首先要熟悉 Python 的基本数据结构和内置函数。像本题中用到的列表、字典(
Counter返回的类似字典的对象)以及列表推导式等都是非常基础且常用的内容,需要熟练掌握它们的用法和特性。 - 深入学习
collections库和heapq模块。了解Counter的各种应用场景,比如统计字符串中字符的出现频率等。对于heapq,不仅要知道如何构建堆、向堆中插入元素和从堆中弹出元素,还要理解堆的特性(如最小堆的堆顶是最小元素)以及在不同问题中的应用方式。可以通过多做一些类似的练习题,如找出数组中前k小的元素(此时可构建最大堆)等,来加深对这些知识点的理解和运用能力。 - 在遇到算法问题时,要学会分析问题的本质需求,像本题就是要找出前
k高频率的元素,然后思考有哪些数据结构和算法可以满足这个需求。不要急于动手写代码,先在脑海中构思好大致的解决思路,比如本题先想到统计频率,再想到用堆来筛选,这样可以避免走很多弯路,提高代码的编写效率和质量。
- 对于刚入门的同学来说,首先要熟悉 Python 的基本数据结构和内置函数。像本题中用到的列表、字典(