查找热点数据(困难)

114 阅读4分钟

问题描述

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按任意顺序返回答案。

  • 1 <= nums.length <= 10^5
  • k 的取值范围是 [1, 数组中不相同的元素的个数]
  • 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的

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

示例 1

输入: nums = [1,1,1,2,2,3], k = 2

输出: [1,2]

示例 2

输入: nums = [1], k = 1

输出: [1]

问题分析:

1、参数:

vector& nums: 输入的整数数组。 int k: 需要返回的前k个高频元素的数量。 2、局部变量:

unordered_map<int, int> freqMap: 用于存储每个数字的频率。 vector<pair<int,int>> result: 存储哈希表中的键值对(数字及其频率)。 stringstream ss: 用于构建最终返回的字符串。 3、逻辑流程:

构建频率映射: 遍历输入数组 nums,使用 unordered_map 记录每个数字的频率。 存储映射到结果向量: 将 freqMap 中的每个键值对(数字及其频率)添加到 result 向量中。 排序: 使用 sort 函数对 result 向量进行排序,排序依据是元素的频率(降序)。 构建返回字符串: 遍历排序后的 result 向量的前 k 个元素,将它们转换为字符串并使用逗号分隔,存储在 stringstream 中。 返回结果: 将 stringstream 中的内容转换为字符串并返回。

二、解题思路

为了满足时间复杂度的要求,可以利用哈希表(字典)来统计每个元素在数组中的出现频率,然后通过构建小顶堆(或大顶堆)来找出出现频率前 k 高的元素。具体步骤如下:

  1. 统计元素出现频率

    • 使用哈希表(例如在 Python 中可以使用字典)来遍历数组 nums,将每个元素作为键,该元素在数组中出现的次数作为值,进行频率统计。例如,对于数组 nums = [1,1,1,2,2,3],经过统计后哈希表可能为 {1: 3, 2: 2, 3: 1}
  2. 构建小顶堆(或大顶堆)

    • 方法一:构建小顶堆

      • 可以创建一个大小为 k 的小顶堆。遍历哈希表中的每个元素及其频率,将元素频率对作为元组(例如 (频率, 元素))压入小顶堆中。如果小顶堆的大小超过了 k,则弹出堆顶元素(因为小顶堆的堆顶是当前堆中频率最小的元素对)。这样,在遍历完哈希表后,小顶堆中就保留了出现频率前 k 高的元素对应的频率和元素信息。
      • 例如,对于哈希表 {1: 3, 2: 2, 3: 1},假设 k = 2,当遍历到 (3, 1) 时,小顶堆为空,直接压入;当遍历到 (2, 2) 时,小顶堆未满,压入;当遍历到 (1, 3) 时,小顶堆已满,比较 (1, 3) 的频率 1 和小顶堆堆顶元素(假设此时堆顶为 (2, 2))的频率 2,因为 1 < 2,所以弹出堆顶元素 (2, 2),然后压入 (1, 3)。最终小顶堆中保留的是出现频率前 k 高的元素对应的信息。
    • 方法二:构建大顶堆

      • 同样创建一个大小为 k 的堆,但这次构建大顶堆。将元素频率对(例如 (频率, 元素))压入大顶堆中。如果大顶堆的大小超过了 k,则弹出堆顶元素(因为大顶堆的堆顶是当前堆中频率最大的元素对)。与小顶堆不同的是,这里在遍历完哈希表后,需要将大顶堆中的元素全部弹出并反转顺序,才能得到出现频率前 k 高的元素。因为大顶堆中堆顶是频率最高的元素,而我们要的是按任意顺序的前 k 高频率元素,所以弹出并反转顺序后就可以得到符合要求的结果。
  3. 提取前 k 高频率元素

    • 对于构建小顶堆的方法,直接从小顶堆中取出元素部分(即每个元组中的第二个元素),组成列表返回即可。例如,小顶堆中剩下的元素对可能是 [(3, 1), (2, 2)],则返回的列表为 [1, 2]

    • 对于构建大顶堆的方法,将大顶堆中的元素全部弹出并反转顺序后,取出元素部分组成列表返回。例如,大顶堆弹出并反转顺序后的元素对可能是 [(3, 1), (2, 2)],同样返回列表 [1, 2]

    import java.util.*; public class Main { public static String solution(int[] nums, int k) { // 使用哈希表记录每个元素的频率 Map<Integer, Integer> freqMap = new HashMap<>(); for (int num : nums) { freqMap.put(num, freqMap.getOrDefault(num, 0) + 1); }

        // 将频率存储到列表中并按频率排序
        List<Map.Entry<Integer, Integer>> resultList = new ArrayList<>(freqMap.entrySet());
        resultList.sort((a, b) -> b.getValue().compareTo(a.getValue())); // 按频率降序排序
    
        // 创建结果字符串
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < k; i++) {
            sb.append(resultList.get(i).getKey());
            if (i < k - 1) {
                sb.append(","); // 添加逗号分隔
            }
        }
    
        return sb.toString(); // 返回结果字符串
    }
    
    public static void main(String[] args) {
        // 测试用例
        int[] nums1 = {1, 1, 1, 2, 2, 3};
        int[] nums2 = {1};
    
        // 输出结果是否与预期相符
        System.out.println(solution(nums1, 2).equals("1,2")); // true
        System.out.println(solution(nums2, 1).equals("1")); // true
    }
    

    }