问题描述
给你一个整数数组 nums 和一个整数 k,请你用一个字符串返回其中出现频率前 k 高的元素。请按升序排列。
你所设计算法的时间复杂度必须优于 O(n log n),其中 n 是数组大小。
测试样例
样例1:
输入:
nums = [1, 1, 1, 2, 2, 3], k = 2
输出:"1,2"
解释:元素 1 出现了 3 次,元素 2 出现了 2 次,元素 3 出现了 1 次。因此前两个高频元素是 1 和 2。
样例2:
输入:
nums = [1], k = 1
输出:"1"
样例3:
输入:
nums = [4, 4, 4, 2, 2, 2, 3, 3, 1], k = 2
输出:"2,4"
解题思路
- 统计频率:首先,我们需要统计数组中每个元素的出现频率。可以使用
collections.Counter来快速实现这一点。 - 排序:然后,我们需要根据频率对元素进行排序。由于题目要求时间复杂度优于
O(n log n),我们可以使用堆(heap)来实现这一点。 - 选择前 k 个:最后,我们从堆中取出前
k个元素,并按升序排列。
堆的概念
堆是一种特殊的数据结构,通常被表示为一棵完全二叉树,其节点按照某种顺序排列,以满足特定的性质。堆的主要性质包括:
- 完全二叉树:堆的物理结构本质上是顺序存储的,即使用一维数组来表示。逻辑上,堆是一棵完全二叉树,这意味着除了最后一层外,每一层都被完全填满,并且最后一层的节点都尽可能地向左对齐12。
- 节点值的有序性:堆中某个节点的值总是不大于(对于最大堆)或不小于(对于最小堆)其父节点的值。这意味着根节点是序列中的最大值(对于最大堆)或最小值(对于最小堆)12。
堆的实现逻辑
堆的实现通常涉及以下操作:
- 向下调整算法:从一个非堆的数组开始,通过调整每个节点的值,使其满足堆的性质。具体步骤是从最后一个非叶子节点开始,向上调整每个节点,直到根节点2。
- 建堆:给定一个数组,通过向下调整算法将其构建成一个堆。这个过程从最后一个非叶子节点开始,向上调整每个节点,直到根节点2。
- 插入操作:在堆的末尾插入一个新元素后,通过向上调整算法将新元素移动到合适的位置,以满足堆的性质23。
- 删除操作:删除堆顶元素(即根节点),将其与最后一个元素交换,然后通过向下调整算法调整新的根节点,以满足堆的性质23。
代码题解
from collections import Counter
import heapq
def solution(nums, k):
# 统计每个元素的频率
frequency = Counter(nums)
# 使用堆来获取前 k 个高频元素
# 这里我们使用最小堆,因为我们只需要保持堆的大小为 k
min_heap = []
for num, freq in frequency.items():
# 将元素和频率的负值(因为 heapq 是最小堆)放入堆中
heapq.heappush(min_heap, (freq, num))
# 如果堆的大小超过 k,弹出堆顶元素(最小频率的元素)
if len(min_heap) > k:
heapq.heappop(min_heap)
# 从堆中取出前 k 个元素,并按升序排列
result = [num for _, num in min_heap]
result.sort()
return result
if __name__ == "__main__":
# 你可以添加更多测试用例
print(solution([1, 1, 1, 2, 2, 3], 2) == [1, 2])
print(solution([1], 1) == [1])