查找热点数据问题的题目解析| 青训营X豆包MarsCode 技术训练营

35 阅读4分钟

问题描述

给你一个整数数组 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"

def solution(nums, k):
    # Please write your code here
    return -2
if __name__ == "__main__":
    #  You can add more test cases here
    print(solution( [1,1,1,2,2,3], 2) == "1,2" )
    print(solution( [1], 1) == "1" )

要解决这个问题,需要找到频率最高的 ( k ) 个元素,并按升序排序,最后以字符串格式返回结果。

由于题目要求时间复杂度必须优于 ( O(n \log n) ),我们需要利用线性时间复杂度的方法,例如使用 桶排序


解题思路

  1. 统计频率
    使用哈希表(字典)统计每个元素的出现次数。这一步的时间复杂度是 ( O(n) )。

  2. 选择前 ( k ) 个高频元素

    • 可以使用 最小堆 来高效找到频率前 ( k ) 高的元素。
    • 堆的插入和弹出操作的时间复杂度是 ( O(\log k) )。因此总时间复杂度是 ( O(n \log k) )。
  3. 结果处理

    • 将前 ( k ) 个高频元素按升序排列,时间复杂度是 ( O(k \log k) )。
    • 最后将排序后的结果转换为字符串,按要求格式返回。

代码实现

from collections import Counter
import heapq

def solution(nums, k):
    # 统计每个数字的频率
    freq = Counter(nums)
    
    # 使用最小堆找到频率前 k 高的元素
    min_heap = []
    for num, count in freq.items():
        heapq.heappush(min_heap, (count, num))
        if len(min_heap) > k:
            heapq.heappop(min_heap)
    
    # 从堆中提取结果并按升序排列
    result = sorted([num for _, num in min_heap])
    
    # 返回结果字符串
    return ",".join(map(str, result))

# 测试
if __name__ == "__main__":
    print(solution([1, 1, 1, 2, 2, 3], 2) == "1,2")  # 输出: "1,2"
    print(solution([1], 1) == "1")  # 输出: "1"
    print(solution([4, 4, 4, 2, 2, 2, 3, 3, 1], 2) == "2,4")  # 输出: "2,4"

代码详解

  1. 统计频率
    使用 collections.Counter 统计数组中每个元素的出现次数。结果是一个键值对,键是数组元素,值是其出现的次数。

    freq = Counter(nums)
    
  2. 最小堆操作

    • 遍历 freq.items(),将每个元素及其频率作为元组 ((频率, 元素)) 压入最小堆。
    • 如果堆的大小超过 ( k ),则弹出堆顶(即频率最小的元素)。
    • 最终堆中保留的就是频率前 ( k ) 高的元素。
    min_heap = []
    for num, count in freq.items():
        heapq.heappush(min_heap, (count, num))
        if len(min_heap) > k:
            heapq.heappop(min_heap)
    
  3. 排序与格式化

    • 从堆中提取出数字,按升序排序。
    • 将排序结果转换为字符串并返回。
    result = sorted([num for _, num in min_heap])
    return ",".join(map(str, result))
    

时间复杂度分析

  1. 统计频率:( O(n) )
    遍历数组统计频率。

  2. 堆操作:( O(n \log k) )
    遍历字典插入堆,每次操作的复杂度是 ( O(\log k) )。

  3. 排序结果:( O(k \log k) )
    对堆中 ( k ) 个元素进行排序。

总体时间复杂度为: [ O(n) + O(n \log k) + O(k \log k) \approx O(n \log k) ]


测试结果

测试用例 1
nums = [1, 1, 1, 2, 2, 3]
k = 2
  • 频率统计:({1: 3, 2: 2, 3: 1})
  • 堆中保留:((2, 2), (3, 1))
  • 排序后结果:("1,2")
测试用例 2
nums = [1]
k = 1
  • 频率统计:({1: 1})
  • 堆中保留:((1, 1))
  • 排序后结果:("1")
测试用例 3
nums = [4, 4, 4, 2, 2, 2, 3, 3, 1]
k = 2
  • 频率统计:({4: 3, 2: 3, 3: 2, 1: 1})
  • 堆中保留:((3, 2), (3, 4))
  • 排序后结果:("2,4")

总结

以上代码高效地解决了问题,符合 ( O(n \log k) ) 的时间复杂度要求,且测试结果与题目期望一致。如有更大数据的性能测试需求,也可进一步验证堆操作的效率。