查找热点数据问题(困难) | 豆包MarsCode AI刷题

85 阅读6分钟

问题描述

给你一个整数数组 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))
  1. 使用一个哈希表(字典)来统计数组nums中每个元素出现的次数。
  2. 将这个字典转换成一个列表,其中每个元素是一个元组,包含原数组中的值及其对应的频率。
  3. 对这个列表根据频率进行排序。如果两个元素的频率相同,则按数值大小排序。
  4. 从排序后的列表中提取前k个元素,并将它们按升序排列。
  5. 将这些元素转换为字符串格式并返回。

用的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]
  1. 统计每个数字的出现频率:首先遍历输入数组nums,使用一个字典frequency_map来记录每个数字及其出现的次数。
  2. 创建桶结构:基于频率创建一个列表bucket,其中索引表示频率,值为在该频率下出现的所有数字的列表。这样做的目的是为了能够快速地根据频率获取对应的数字集合,而不需要对整个频率字典进行排序。
  3. 从高频到低频收集结果:从后向前遍历bucket(即从最大可能的频率开始),将当前频率下的所有数字添加到结果列表result中。如果结果列表中的元素数量达到了k,则停止处理剩余的频率。对于每个频率,确保将对应频率的数字先进行排序再添加到结果列表中,以满足题目要求的最终输出是按数值大小升序排列的。
  4. 返回结果:最后,返回结果列表result的前k个元素作为答案。

方法一:使用 Counter 和排序

  1. 统计频率

    • 使用 Counter 来统计频率的时间复杂度是 O(n),其中 n 是 nums 的长度。
  2. 排序

    • 对 count.items() 进行排序的时间复杂度是 O(m log m),其中 m 是不同元素的数量。在最坏情况下,m 可以达到 n(当所有元素都不同)。
  3. 提取前 k 个元素并排序

    • 提取最后 k 个元素的时间复杂度是 O(k)。
    • 再次对这 k 个元素进行排序的时间复杂度是 O(k log k)。

总的时间复杂度大致为 O(n + m log m + k log k)。如果 m 接近 n,则时间复杂度接近 O(n log n)。

方法二:使用桶排序

  1. 统计频率

    • 时间复杂度是 O(n)。
  2. 创建桶

    • 创建桶的时间复杂度是 O(n),因为需要遍历频率字典并将元素放入相应的桶中。
  3. 从高频到低频收集结果

    • 遍历桶的时间复杂度是 O(n),因为在最坏情况下可能需要遍历所有的桶。
    • 每个桶内的元素排序的时间复杂度是 O(f log f),其中 f 是该频率下的元素数量。在最坏情况下,这个操作对于每个频率都要执行一次。但由于每个元素只属于一个频率,所以总的排序操作不会超过 O(n log n)。

总的时间复杂度大约为 O(n + n log n) = O(n log n)。

matplotlib可视化这两个算法的区别:

image.png

在1-10000的时候,两个算法时间交替,是差不多的,在10000之后Counter+Sort优于Bucket Sort。