查找热点数据问题
问题描述
给你一个整数数组 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"
解决思路
- 频率统计:首先,需要统计数组中每个元素出现的次数。我们可以使用
collections.Counter来实现这个功能,它可以在 O(n) 的时间复杂度内计算频率,其中 n 是数组的大小。 - 堆排序:为了找出频率前 k 高的元素,使用堆(特别是最小堆)是一个非常合适的选择。通过
heapq.nlargest函数,我们可以高效地获取前 k 个频率最大的元素。heapq.nlargest的时间复杂度是 O(n log k),这比 O(n log n) 更优。 - 升序排序:获取到前 k 个元素后,我们需要按升序对这些元素进行排序。由于 k 的值相对较小,排序的时间复杂度为 O(k log k),这通常是可以接受的。
- 结果输出:将最终的元素按要求的格式拼接成字符串,元素之间用逗号分隔。
代码实现
import collections
import heapq
def solution(nums, k):
count = collections.Counter(nums)
top_k = heapq.nlargest(k, count.keys(), key=lambda x: (count[x], -x)) # 获取前k个频率最大的元素
top_k.sort() # 升序排序
return ",".join(map(str, top_k)) # 结果转换为字符串格式
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")
代码解释
collections.Counter(nums):用来统计数组nums中每个元素的频率,返回一个字典,键是元素,值是出现的次数。heapq.nlargest(k, count.keys(), key=lambda x: (count[x], -x)):这个函数用来获取出现频率前 k 高的元素。count.keys()提供了所有不同的元素,key=lambda x: (count[x], -x)用来按照频率排序,如果频率相同,则按元素的值降序排列,确保结果的稳定性。top_k.sort():对前 k 个高频元素按升序进行排序,以满足题目要求。",".join(map(str, top_k)):将排序后的元素列表转为字符串,并以逗号分隔。
复杂度分析:
-
时间复杂度:
- 统计频率:O(n),其中 n 是数组的长度。
- 使用
heapq.nlargest获取前 k 个元素:O(n log k)。 - 排序前 k 个元素:O(k log k)。
- 总时间复杂度:O(n log k),比 O(n log n) 更优。
-
空间复杂度:O(n),主要用于存储频率计数。
总结
- 思路:通过频率统计和堆排序实现高效的查找前 k 个高频元素。
- 复杂度分析:时间复杂度为 O(n log k),空间复杂度为 O(n)。
- 优化:相比传统的 O(n log n) 排序方法,O(n log k) 的方法能够显著减少计算量,特别是当 k 比 n 小的时候。