问题描述
给你一个整数数组 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"
这个问题是典型的“前 kk 高频元素”问题。需要注意的是,要求时间复杂度优于 O(nlogn)O(n \log n),这表明不能通过简单的排序来解决,需要利用 哈希表 和 堆 的结合来完成。
以下为解题思路
解题思路
-
使用哈希表统计频率:
- 遍历数组
nums,统计每个数字出现的次数。 - 构造一个字典
freq_map,键为数字,值为对应的出现频率。
- 遍历数组
-
用堆(Heap)寻找前 kk 高频元素:
-
使用 Python 的
heapq模块,结合小根堆来维护频率最高的 k 个元素。 -
构造一个大小为 k 的小根堆:
- 如果堆的大小超过 k,将堆顶元素(频率最小的)弹出,从而始终保持堆中元素为频率最高的前 k 个。
-
-
提取结果并排序:
- 将堆中的 k 个元素提取出来,并按升序排列(题目要求)。
- 转换为字符串格式输出,元素之间用逗号分隔。
-
时间复杂度分析:
- 统计频率:O(n)。
- 构建堆:对于每个元素,最多 O(logk),总复杂度为 O(nlogk)。
- 排序结果:O(klogk)。
- 总复杂度:O(nlogk),满足题目要求优于 O(nlogn)。
代码实现
以下是 Python 的代码实现:
from collections import Counter
import heapq
def top_k_frequent(nums, k):
# 1. 统计每个数字的频率
freq_map = Counter(nums)
# 2. 使用小根堆寻找频率前 k 高的元素
heap = []
for num, freq in freq_map.items():
heapq.heappush(heap, (freq, num)) # 按 (频率, 元素) 入堆
if len(heap) > k: # 堆的大小超过 k 时弹出最小频率的元素
heapq.heappop(heap)
# 3. 提取堆中元素并按升序排序
result = sorted([num for freq, num in heap])
# 4. 转换为字符串并返回
return ",".join(map(str, result))
# 测试样例
print(top_k_frequent([1, 1, 1, 2, 2, 3], 2)) # 输出: "1,2"
print(top_k_frequent([1], 1)) # 输出: "1"
print(top_k_frequent([4, 4, 4, 2, 2, 2, 3, 3, 1], 2)) # 输出: "2,4"
测试结果
-
样例1:
- 输入:
nums = [1, 1, 1, 2, 2, 3], k = 2 - 输出:
"1,2" - 解释:1 出现 3 次,2 出现 2 次,选择这两个高频元素并按升序排序。
- 输入:
-
样例2:
- 输入:
nums = [1], k = 1 - 输出:
"1" - 解释:只有一个元素 1,直接输出。
- 输入:
-
样例3:
- 输入:
nums = [4, 4, 4, 2, 2, 2, 3, 3, 1], k = 2 - 输出:
"2,4" - 解释:2 和 4 都出现了 3 次,选择这两个高频元素并按升序排序。
- 输入:
详细解释代码逻辑
-
统计频率:
- 使用
Counter(哈希表)来统计每个元素的出现频率,频率存储在freq_map中。 - 例如:
nums = [4, 4, 4, 2, 2, 2, 3, 3, 1]的频率表为{4: 3, 2: 3, 3: 2, 1: 1}。
- 使用
-
维护小根堆:
- 将元素和其频率作为元组存入堆中,频率作为比较的第一关键字。
- 堆的大小始终为 k,超过 k 时弹出频率最小的元素。
- 例如,当 k=2 时,堆最终为 (3,2),(3,4)。
-
提取结果并排序:
- 从堆中提取元素(仅取数字部分),形成列表。
- 使用
sorted函数按升序排序后输出。
-
转换为字符串:
- 使用
",".join(map(str, result))将列表元素转换为字符串,并用逗号连接。
- 使用
时间复杂度分析
-
统计频率:O(n),遍历数组构建频率表。
-
堆操作:对于每个不同的元素,进行堆操作:
- 每次插入或弹出堆的复杂度为 O(logk)。
- 总复杂度为 O(nlogk)。
-
排序:O(klogk),堆中 k 个元素的排序。
-
总复杂度:O(nlogk+klogk)。
总结
- 本问题通过频率统计与堆的结合,满足了时间复杂度 O(nlogk) 的要求。
- 使用 Python 提供的
Counter和heapq工具,大大简化了实现。 - 堆的大小限制为 k,在处理大规模数据时也能保证高效性。