思路分析
-
统计频率: 在许多编程问题中,统计元素的出现频率是解决问题的第一步。我们可以使用 Python 中的
collections.Counter来非常方便地统计数组或其他序列中每个元素的频率。Counter是一个字典的子类,其键值对分别表示元素和其出现的次数。通过这种方式,我们可以快速获得每个元素的出现频率,从而为后续处理打下基础。例如,如果我们有一个包含数字的列表
nums = [1, 1, 1, 2, 2, 3],我们可以通过Counter(nums)得到频率映射Counter({1: 3, 2: 2, 3: 1})。这一步的时间复杂度为 O(n),其中n是数组的长度。 -
构建频率映射: 使用
Counter类得到的频率映射本质上是一个字典,键为数组元素,值为对应的频率。例如,在Counter({1: 3, 2: 2, 3: 1})中,数字 1 出现了 3 次,数字 2 出现了 2 次,数字 3 出现了 1 次。频率映射为我们后续选择频率最高的元素提供了基础数据结构。对于频率映射的构建,时间复杂度为 O(n),因为我们只需要遍历一次输入数组来统计每个元素的出现次数。
-
使用堆选择高频元素: 要找出频率最高的
k个元素,我们使用最小堆(Min-Heap)来实现。最小堆是一种二叉堆,它始终保证堆顶元素是堆中的最小元素。在这个问题中,我们将每个元素的频率作为堆的优先级,并使用堆来维护频率最高的k个元素。具体来说,堆的大小始终保持为
k,我们遍历频率字典,对于每一个元素,如果堆的大小小于k,我们将元素加入堆中;如果堆的大小已经是k,并且当前元素的频率大于堆顶元素的频率,我们就弹出堆顶元素,并将当前元素插入堆中。通过这种方式,堆顶始终保存着频率最小的元素,确保堆中的k个元素是频率最高的。heapq模块是 Python 提供的一个堆队列算法库,其中的heappush用于将元素加入堆,heappop用于从堆中弹出最小元素。在最坏情况下,插入和删除操作的时间复杂度是 O(log k),因此我们对每一个元素执行的堆操作的总时间复杂度为 O(n log k),其中n是数组的长度,k是我们需要找出的元素个数。 -
排序输出: 在得到频率最高的
k个元素之后,我们还需要按升序对这些元素进行排序。因为题目要求返回的元素是升序排列的,所以我们可以直接使用 Python 内置的sort()方法对结果进行排序。排序的时间复杂度为 O(k log k),因为最终结果的大小是k,最多对k个元素进行排序。 -
时间复杂度分析:
- 统计频率的时间复杂度是 O(n),其中
n是数组的长度。 - 向堆中插入元素的时间复杂度是 O(log k),最多插入
n个元素,所以所有插入操作的总时间复杂度是 O(n log k)。 - 最后的排序操作的时间复杂度是 O(k log k),因为最多有
k个元素需要排序。 - 综合起来,整体的时间复杂度为 O(n log k),其中
n是输入数组的长度,k是我们要找出的频率最高的元素个数。
- 统计频率的时间复杂度是 O(n),其中
-
空间复杂度分析:
- 空间复杂度主要来自于存储频率映射(
Counter返回的字典)和堆。频率映射需要 O(n) 的空间来存储每个元素的频率,堆需要 O(k) 的空间来存储k个频率最高的元素。因此,总的空间复杂度为 O(n + k)。
- 空间复杂度主要来自于存储频率映射(
思路流程图
erDiagram
START ||--o{ COUNTER : starts
COUNTER ||--o{ CREATE_HEAP : creates
CREATE_HEAP ||--o{ ITERATE_FREQUENCY : iterates
ITERATE_FREQUENCY ||--o{ INSERT_TO_HEAP : inserts
INSERT_TO_HEAP ||--o{ CHECK_HEAP_SIZE : checks
CHECK_HEAP_SIZE ||--o{ REMOVE_MIN_ELEMENT : removes
CHECK_HEAP_SIZE ||--o{ INSERT_NEXT_ELEMENT : inserts_next
REMOVE_MIN_ELEMENT ||--o{ INSERT_NEXT_ELEMENT : continues
INSERT_NEXT_ELEMENT ||--o{ EXTRACT_TOP_K : extracts
EXTRACT_TOP_K ||--o{ SORT_RESULT : sorts
SORT_RESULT ||--o{ CONVERT_TO_STRING : converts
CONVERT_TO_STRING ||--o{ RETURN_RESULT : returns
RETURN_RESULT ||--o{ END : ends
代码
import heapq
from collections import Counter
def solution(nums, k):
# 统计每个元素的频率
freq_map = Counter(nums)
# 使用最小堆来保存频率最高的k个元素
heap = []
# 遍历频率字典,将每个元素和频率放入堆中
for num, freq in freq_map.items():
heapq.heappush(heap, (freq, num))
# 如果堆的大小超过k,移除堆中频率最小的元素
if len(heap) > k:
heapq.heappop(heap)
# 从堆中取出前k个频率最大的元素
result = [num for _, num in heap]
# 按元素值升序排列
result.sort()
# 将结果转换为字符串,并用逗号连接
return ",".join(map(str, result))
if __name__ == "__main__":
# 测试用例
print(solution([1, 1, 1, 2, 2, 3], 2)) # 输出:"1,2"
print(solution([1], 1)) # 输出:"1"
print(solution([4, 4, 4, 2, 2, 2, 3, 3, 1], 2)) # 输出:"2,4"