问题描述
给你一个整数数组 nums 和一个整数 k,请你用一个字符串返回其中出现频率前 k 高的元素。请按升序排列。
你所设计算法的时间复杂度必须优于 O(n log n),其中 n 是数组大小。
输入
- nums: 一个正整数数组
- k: 一个整数
返回
返回一个包含 k 个元素的字符串,数字元素之间用逗号分隔。数字元素按升序排列,表示出现频率最高的 k 个元素。
参数限制
- 1 <= nums[i] <= 10^4
- 1 <= nums.length <= 10^5
- k 的取值范围是 [1, 数组中不相同的元素的个数]
- 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的
测试样例
样例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"
题目分析
我们需要从一个整数数组 nums 中找出出现频率最高的 k 个元素,并且返回这些元素按升序排列的字符串结果。我们需要知道每个元素在数组中的出现次数。在得到每个元素的频率后,我们需要选出频率前 k 高的元素。返回的元素需要按升序排列。时间复杂度必须优于 O(n log n),即要求一个比排序更高效的解决方案。
解题思路
使用 最小堆 来获取频率最高的 k 个元素,并通过堆的特性保持堆大小为 k,从而确保我们能够在时间复杂度上优化。
代码分析
- 统计每个元素的频率
python
代码解读
复制代码
frequency = Counter(nums)
- 使用堆来获取频率最高的 k 个元素,这里我们使用最小堆,因为 heapq 只支持最小堆,可以通过将频率取负来模拟最大堆
python
代码解读
复制代码
min_heap = []
for num, freq in frequency.items():
heapq.heappush(min_heap, (freq, num))
if len(min_heap) > k:
heapq.heappop(min_heap)
- 从堆中提取元素并按升序排列,注意:堆中的元素是 (freq, num) 的形式,我们需要按 num 排序
python
代码解读
复制代码
result = [item[1] for item in min_heap]
result.sort()
- 最后,将结果转换为字符串并返回
python
代码解读
复制代码
return ','.join(map(str, result))
完整代码如下:
python
代码解读
复制代码
from collections import Counter
import heapq
def solution(nums, k):
frequency = Counter(nums)
min_heap = []
for num, freq in frequency.items():
heapq.heappush(min_heap, (freq, num))
if len(min_heap) > k:
heapq.heappop(min_heap)
result = [item[1] for item in min_heap]
result.sort()
return ','.join(map(str, result))
总结
时间复杂度:
- 频率统计:O(n),通过
Counter来统计元素的频率。 - 最小堆操作:O(n log k),我们将每个元素放入堆中,而堆的大小最多为
k,每次操作的复杂度是 O(log k)。 - 排序:O(k log k),我们需要对堆中的
k个元素按升序排序。 - 转换为字符串:O(k),将
k个元素转化为字符串。
因此,整体的时间复杂度是 O(n log k + k log k) ,其中 n 是数组的长度,k 是需要返回的高频元素个数。由于 k 小于或等于 n,在最坏情况下,排序操作 O(k log k) 是比较小的,整体复杂度主要由 O(n log k) 主导。
空间复杂度:
- 频率统计:O(n),
Counter需要存储每个元素的频率。 - 小堆:O(k),堆中最多存储
k个元素。 - 结果列表:O(k),用于存储最终的结果。
因此,空间复杂度为 O(n + k) 。
本次使用 最小堆来获取频率最高的 k 个元素,并通过堆的特性保持堆大小为 k。相比于直接排序整个数组,使用堆优化了获取前 k 高频元素的过程,时间复杂度为 O(n log k),而不是 O(n log n)。即使 k 比较小时(例如 k 很小,n 很大),堆的操作仍然保持高效。