青训营伴学笔记丨MarsCode刷题日记丨查找热点数据问题

37 阅读3分钟

问题描述

给你一个整数数组 nums 和一个整数 k,请你用一个字符串返回其中出现频率前 k 高的元素。请按升序排列。

你所设计算法的时间复杂度必须优于 O(n log n),其中 n 是数组大小。

测试样例

样例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"

解题思路

  1. 统计频率:首先,我们需要统计数组中每个元素的出现频率。可以使用 collections.Counter 来快速实现这一点。
  2. 排序:然后,我们需要根据频率对元素进行排序。由于题目要求时间复杂度优于 O(n log n),我们可以使用堆(heap)来实现这一点。
  3. 选择前 k 个:最后,我们从堆中取出前 k 个元素,并按升序排列。

堆的概念

‌是一种特殊的数据结构,通常被表示为一棵完全二叉树,其节点按照某种顺序排列,以满足特定的性质。堆的主要性质包括:

  1. 完全二叉树‌:堆的物理结构本质上是顺序存储的,即使用一维数组来表示。逻辑上,堆是一棵完全二叉树,这意味着除了最后一层外,每一层都被完全填满,并且最后一层的节点都尽可能地向左对齐‌12。
  2. 节点值的有序性‌:堆中某个节点的值总是不大于(对于最大堆)或不小于(对于最小堆)其父节点的值。这意味着根节点是序列中的最大值(对于最大堆)或最小值(对于最小堆)‌12。

堆的实现逻辑

堆的实现通常涉及以下操作:

  • 向下调整算法‌:从一个非堆的数组开始,通过调整每个节点的值,使其满足堆的性质。具体步骤是从最后一个非叶子节点开始,向上调整每个节点,直到根节点‌2。
  • 建堆‌:给定一个数组,通过向下调整算法将其构建成一个堆。这个过程从最后一个非叶子节点开始,向上调整每个节点,直到根节点‌2。
  • 插入操作‌:在堆的末尾插入一个新元素后,通过向上调整算法将新元素移动到合适的位置,以满足堆的性质‌23。
  • 删除操作‌:删除堆顶元素(即根节点),将其与最后一个元素交换,然后通过向下调整算法调整新的根节点,以满足堆的性质‌23。

代码题解

from collections import Counter
import heapq

def solution(nums, k):
    # 统计每个元素的频率
    frequency = Counter(nums)
    
    # 使用堆来获取前 k 个高频元素
    # 这里我们使用最小堆,因为我们只需要保持堆的大小为 k
    min_heap = []
    for num, freq in frequency.items():
        # 将元素和频率的负值(因为 heapq 是最小堆)放入堆中
        heapq.heappush(min_heap, (freq, num))
        # 如果堆的大小超过 k,弹出堆顶元素(最小频率的元素)
        if len(min_heap) > k:
            heapq.heappop(min_heap)
    
    # 从堆中取出前 k 个元素,并按升序排列
    result = [num for _, num in min_heap]
    result.sort()
    
    return result

if __name__ == "__main__":
    # 你可以添加更多测试用例
    print(solution([1, 1, 1, 2, 2, 3], 2) == [1, 2])
    print(solution([1], 1) == [1])