查找热点数据问题
问题描述
给你一个整数数组 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
1. 题目解析
在这道题中,我们被要求从给定的整数数组中找出出现频率最高的 k 个元素,并按升序排列。要求算法的时间复杂度优于 O(n log n),因此我们需要避免使用 sort() 等 O(n log n) 复杂度的排序算法,而应该通过高效的数据结构和算法来解决问题。
题目分析与思路:
- 我们首先需要统计数组中各个元素的出现频率。Python 的
collections.Counter类可以帮助我们快速完成这一任务,时间复杂度为O(n),其中n是数组的长度。 - 接着,我们需要找出出现频率最高的
k个元素。常规的方法是使用排序,但考虑到我们要求时间复杂度优于O(n log n),可以使用heapq.nlargest()函数来获取频率最高的k个元素。这个函数的时间复杂度是O(n log k),比排序要高效得多。 - 最后,题目要求输出结果时要按升序排列,因此我们在得到频率最高的
k个元素后,需对其进行排序,时间复杂度是O(k log k),通常k的值远小于n。
算法步骤:
- 统计元素频率:利用
Counter对数组进行计数,得到每个元素的频次。 - 获取前 k 个高频元素:使用
heapq.nlargest(k, ...)获取频率最高的k个元素。 - 排序结果:对
k个高频元素按升序排序并返回。
代码实现:
from collections import Counter
import heapq
def solution(nums, k):
# 1. 统计频率
frequency = Counter(nums)
# 2. 使用堆来找出频率前 k 的元素
# 这里使用 heapq.nlargest 获取频率前 k 的元素
top_k = heapq.nlargest(k, frequency.keys(), key=lambda x: frequency[x])
# 3. 对结果进行排序
return ','.join(map(str, sorted(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"
代码分析:
Counter(nums):这个操作的时间复杂度是O(n),用于统计每个元素的出现次数。heapq.nlargest(k, frequency.keys(), key=lambda x: frequency[x]):这里使用heapq.nlargest获取出现频率最高的k个元素。该操作的时间复杂度是O(n log k),比排序更高效。sorted(top_k):最后对k个元素按升序排序,时间复杂度是O(k log k),一般情况下,k的大小远小于n,因此排序操作的影响较小。
2. 知识总结
通过解这道题,我总结了一些提高刷题效率和算法设计的关键点:
(1) 频率统计的技巧
- 在很多与统计相关的题目中,
collections.Counter是一个非常有用的工具。它能够高效地统计数组中每个元素的出现次数,并且返回一个字典形式的数据结构,其中键为元素,值为频次。
(2) 堆的使用
- 在解决“前 k 高频元素”类问题时,使用堆是一个很常见的技巧。通过堆可以在
O(n log k)的时间内快速找到频率最高的k个元素,避免了全数组排序的开销。 - Python 提供的
heapq.nlargest函数,能够根据自定义的比较规则,返回前k个元素,并且时间复杂度优于排序方法。
(3) 时间复杂度分析
- 在优化算法时,选择合适的数据结构非常重要。在这个问题中,
Counter和heapq的结合使得我们能够在保证效率的前提下,完成任务。整体的时间复杂度为O(n log k + k log k),其中n为数组的长度,k是要找出的高频元素的个数。对于大多数场景,k会比n小很多,因此这个复杂度是可以接受的。
3. 学习计划
通过豆包MarsCode AI 刷题,我发现了一些高效学习算法的技巧和方法,结合我自己的学习体会,我制定了如下的学习计划:
(1) 刷题目标的制定
- 每周设置一个清晰的刷题目标,例如学习一个特定的数据结构(如堆、哈希表、树)或解决一个具体类型的问题(如排序、查找、动态规划)。
- 每次学习后,确保能独立解决相同类型的题目,并反思自己的解题思路和优化策略。
(2) 针对性学习与定期回顾
- 在刷题过程中,尤其是遇到不会的题目时,可以通过右侧的豆包MarsCode AI,得到一些代码的提示,通过自己的思考写出完整的代码,又或者通过豆包MarsCode AI直接得到整体的思路,自己转换成正确的代码,但更重要的是理解和消化解法。对于未做出的题,应该重点分析原因,重新推演解法,可以手写算法的实现过程,以及伪代码等等。
- 将未能做正确的题进行整理,可以按题型定期回顾和重新练习,以此提高解决类似问题的能力。
(3) 做题后总结
- 每做完一道题目后,都应该总结自己的思路,包括:
- 题目类型和常见解法。
- 自己采用的算法复杂度和优化方法。
- 如果时间允许,尝试对解法进行优化,找到更高效的解法。
- 相关数据结构的应用和实现方式。
4. 工具运用
在学习过程中,除了使用豆包MarsCode AI的功能外,合理利用其他学习资源可以大大提高学习效率:
(1) 在线算法教程和书籍
- 结合在线教程(如《算法导论》、《LeetCode 算法题解》),可以快速理解问题的解法和理论背后的算法原理。
- 书籍的系统性和深入性有助于打好理论基础,而在线刷题则提供了实践的机会。
(2) 与他人讨论和交流
- 与同学或学习伙伴共同讨论问题的解法,可以帮助加深对算法的理解。很多时候,别人的思路可能会提供更高效或者更简洁的解决方案。
通过不断刷题、总结和优化,我相信可以在算法的学习和应用上取得更大的进步。