问题描述
给你一个整数数组 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"
为了高效地解决这个问题,我们需要获取数组中出现频率最高的 k 个元素,并且结果需要按升序排列。为了使算法的时间复杂度优于 O(n log n),我们可以使用一些具有线性时间复杂度的算法。以下是解题思路的详细分析:
解题思路
- 频率统计:我们需要统计每个数字在数组中出现的频率。可以使用哈希表(字典)来实现这一点,这样的复杂度是 O(n),其中 n 是数组的大小。
- 使用最小堆(Heap) :为了提取前 k 个高频元素,我们可以使用最小堆。最小堆的特点是堆顶元素是最小的,因此我们可以在堆中维护当前频率最高的 k 个元素。此步骤的复杂度是 O(m log k),其中 m 是不同元素的数量。
- 构造结果:从堆中取出 k 个元素后,将这些元素按升序排序,然后生成以逗号分隔的字符串。排序的复杂度是 O(k log k)。
结合以上步骤,我们可以得到总的时间复杂度为 O(n + m log k),通常情况下 m <= n。因此,这一复杂度优于 O(n log n)。
具体实现步骤
- 使用一个字典来统计每个元素的频率。
- 使用 Python 的
heapq模块创建一个最小堆,存储频率和元素的元组(频率,元素)。 - 将频率放入堆中,仅保持前 k 个高频元素。
- 从堆中提取元素并排序,然后生成结果字符串。
from collections import Counter
import heapq
def solution(nums, k):
# 统计频率
freq_map = Counter(nums)
# 使用最小堆保存频率最高的 k 个元素
min_heap = []
for num, freq in freq_map.items():
heapq.heappush(min_heap, (freq, num))
if len(min_heap) > k:
heapq.heappop(min_heap)
# 提取出堆中的元素并排序
top_k = [heapq.heappop(min_heap) for _ in range(len(min_heap))]
top_k.sort(key=lambda x: x[1]) # 按元素值升序排序
# 生成结果字符串
result = ",".join(str(num) for freq, num in top_k)
return result
if __name__ == "__main__":
# 测试用例
print(solution([1, 1, 1, 2, 2, 3], 2)) # 输出 "1,2"
print(solution([1], 1)) # 输出 "1"
队列算法是计算机科学中常用的数据结构和算法,主要用于管理按顺序处理的数据。队列遵循先进先出(FIFO)的原则,通常用于任务调度、数据缓冲等诸多场景。以下是一些常见的队列算法和相关概念:
1. 基本队列操作
- 入队(Enqueue):将一个元素添加到队列的尾部。
- 出队(Dequeue):从队列的头部移除并返回一个元素。
- 查看队头(Front/Peek):返回队列头部的元素,但不删除它。
- 判断队列是否为空:检查队列中是否还有元素。
- 获取队列大小:获取当前队列中元素的数量。
2. 循环队列
循环队列是对基本队列的改进,使用数组实现,并通过处理数组的循环性质来有效利用空间。可以避免因队列头部出队而造成的空闲空间。
3. 双端队列(Deque)
双端队列允许在队列两端进行插入和删除操作。支持:
- 从前面或后面入队(Enqueue Front / Enqueue Rear)
- 从前面或后面出队(Dequeue Front / Dequeue Rear)
4. 优先队列
优先队列根据优先级管理元素,而不是仅仅依赖于它们的入队顺序。常用的数据结构有:
- 堆(Heap):通常用最小堆或最大堆实现优先队列,以支持高效的插入和删除最大(或最小)元素操作。
5. 比较队列和栈
尽管队列和栈都是线性数据结构,但它们的操作方式不同:
- 队列:遵循先进先出(FIFO)的原则,适合任务调度等场景。
- 栈:遵循后进先出(LIFO)的原则,适合需要逆序处理的场景,如递归调用、表达式求值等。
6. 队列应用
- 任务调度:用于操作系统的任务管理。
- 资源共享:打印任务、网络请求等。
- 缓冲区:用于数据流(如视频流、数据包)中的缓冲管理。
- 广度优先搜索(BFS):在图算法中,使用队列实现广度优先遍历。
7. 队列的实现
- 数组:使用固定大小的数组实现队列,但需要考虑扩容的问题。
- 链表:使用链表实现,具有动态大小,适应性更强。
- 标准库:许多编程语言提供了内置的队列数据结构,如 Python 的
collections.deque、Java 的LinkedList和PriorityQueue。
8. 线程安全队列
在多线程环境中,线程安全的队列非常重要。很多语言提供了线程安全的队列实现,例如:
- Java 的
BlockingQueue - Python 的
queue.Queue