问题描述
给你一个整数数组 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"
思路一:用Hash表存储每个数据的个数,然后输出前k的数据。
python
代码解读
复制代码
def solution(nums, k):
from collections import Counter
# 统计每个元素出现的次数
count = Counter(nums)
# 根据频率对元素进行排序,如果频率相同则按元素值排序
sorted_items = sorted(count.items(), key=lambda x: (x[1], x[0]))
# 提取前k个最频繁的元素,注意这里是从后往前取k个,因为排序是从小到大
top_k = [item[0] for item in sorted_items[-k:]]
# 按照题目要求,结果需要按升序排列
top_k.sort()
# 转换成字符串形式输出
return ','.join(map(str, top_k))
- 使用一个哈希表(字典)来统计数组
nums中每个元素出现的次数。 - 将这个字典转换成一个列表,其中每个元素是一个元组,包含原数组中的值及其对应的频率。
- 对这个列表根据频率进行排序。如果两个元素的频率相同,则按数值大小排序。
- 从排序后的列表中提取前
k个元素,并将它们按升序排列。 - 将这些元素转换为字符串格式并返回。
用的collections中的Counter,这是Python标准库中Counter类,能快速实现计数功能,但其实也能手搓一个Counter。
对于一个计数器,就是加一个字典,如果nums在字典中,就把这个数加一,如果不存在,就加入这个数值为1.
pyhton
代码解读
复制代码
def counter(nums):
counter_num = {}
for num in nums:
if num in counter_num:
counter_num[num] += 1
else:
counter_num[num] = 1
return counter_num
上面的就可以写成:
python
代码解读
复制代码
def solution(nums, k):
# 统计每个元素出现的次数
count = counter(nums)
# 根据频率对元素进行排序,如果频率相同则按元素值排序
sorted_items = sorted(count.items(), key=lambda x: (x[1], x[0]))
# 提取前k个最频繁的元素,注意这里是从后往前取k个,因为排序是从小到大
top_k = [item[0] for item in sorted_items[-k:]]
# 按照题目要求,结果需要按升序排列
top_k.sort()
# 转换成字符串形式输出
return ','.join(map(str, top_k))
def counter(nums):
counter_num = {}
for num in nums:
if num in counter_num:
counter_num[num] += 1
else:
counter_num[num] = 1
return counter_num
思路二:桶排序的思想
python
代码解读
复制代码
def solution(nums, k):
# 1. 统计每个数字的出现频率
frequency_map = {}
for num in nums:
if num in frequency_map:
frequency_map[num] += 1
else:
frequency_map[num] = 1
# 2. 创建一个数组,索引为频率,值为出现该频率的元素列表
# 注意这里长度为 len(nums) + 1 是因为频率可能从0到len(nums)
bucket = [[] for _ in range(len(nums) + 1)]
for num, freq in frequency_map.items():
bucket[freq].append(num)
# 3. 从高频开始,收集前 k 个频率最高的元素
result = []
# 从最大频率开始向最小频率遍历
for i in range(len(bucket) - 1, -1, -1):
if len(result) >= k: # 如果已经收集到了k个元素,提前结束
break
if bucket[i]: # 当前频率有元素
# 将当前频率的元素按数字大小排序后加入结果列表
result.extend(sorted(bucket[i]))
# 返回前 k 个高频元素
return result[:k]
- 统计每个数字的出现频率:首先遍历输入数组
nums,使用一个字典frequency_map来记录每个数字及其出现的次数。 - 创建桶结构:基于频率创建一个列表
bucket,其中索引表示频率,值为在该频率下出现的所有数字的列表。这样做的目的是为了能够快速地根据频率获取对应的数字集合,而不需要对整个频率字典进行排序。 - 从高频到低频收集结果:从后向前遍历
bucket(即从最大可能的频率开始),将当前频率下的所有数字添加到结果列表result中。如果结果列表中的元素数量达到了k,则停止处理剩余的频率。对于每个频率,确保将对应频率的数字先进行排序再添加到结果列表中,以满足题目要求的最终输出是按数值大小升序排列的。 - 返回结果:最后,返回结果列表
result的前k个元素作为答案。
方法一:使用 Counter 和排序
-
统计频率:
- 使用
Counter来统计频率的时间复杂度是 O(n),其中 n 是nums的长度。
- 使用
-
排序:
- 对
count.items()进行排序的时间复杂度是 O(m log m),其中 m 是不同元素的数量。在最坏情况下,m 可以达到 n(当所有元素都不同)。
- 对
-
提取前 k 个元素并排序:
- 提取最后 k 个元素的时间复杂度是 O(k)。
- 再次对这 k 个元素进行排序的时间复杂度是 O(k log k)。
总的时间复杂度大致为 O(n + m log m + k log k)。如果 m 接近 n,则时间复杂度接近 O(n log n)。
方法二:使用桶排序
-
统计频率:
- 时间复杂度是 O(n)。
-
创建桶:
- 创建桶的时间复杂度是 O(n),因为需要遍历频率字典并将元素放入相应的桶中。
-
从高频到低频收集结果:
- 遍历桶的时间复杂度是 O(n),因为在最坏情况下可能需要遍历所有的桶。
- 每个桶内的元素排序的时间复杂度是 O(f log f),其中 f 是该频率下的元素数量。在最坏情况下,这个操作对于每个频率都要执行一次。但由于每个元素只属于一个频率,所以总的排序操作不会超过 O(n log n)。
总的时间复杂度大约为 O(n + n log n) = O(n log n)。
用matplotlib可视化这两个算法的区别:
在1-10000的时候,两个算法时间交替,是差不多的,在10000之后Counter+Sort优于Bucket Sort。