查找热点数据问题|豆包MarsCode AI刷题

57 阅读3分钟

问题描述

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

思路

  1. 统计频率:首先,我们需要统计数组中每个元素的出现频率。可以使用哈希表(HashMap)来实现这一点。
  2. 找到前 k 个高频元素:使用优先队列(PriorityQueue)来找到频率最高的 k 个元素。优先队列可以帮助我们在 O(n log k) 的时间复杂度内完成这个任务。
  3. 排序并返回结果:将找到的 k 个高频元素按升序排列,并将其转换为字符串格式返回。

代码实现

import java.util.*;

public class Main {
    public static String solution(int[] nums, int k) {
        // 用于存储每个数字及其出现次数
        Map<Integer, Integer> countMap = new HashMap<>();
        for (int num : nums) {
            countMap.put(num, countMap.getOrDefault(num, 0) + 1);
        }

        // 使用优先队列按频率降序排列,如果频率相同则按值升序
        PriorityQueue<Map.Entry<Integer, Integer>> pq = new PriorityQueue<>(
                (a, b) -> a.getValue().equals(b.getValue()) ? a.getKey() - b.getKey() : b.getValue() - a.getValue()
        );

        // 将所有条目添加到优先队列中
        pq.addAll(countMap.entrySet());

        // 提取前k个最频繁的元素
        List<Integer> topKElements = new ArrayList<>();
        for (int i = 0; i < k && !pq.isEmpty(); i++) {
            topKElements.add(pq.poll().getKey());
        }

        // 对结果列表中的元素进行自然排序
        Collections.sort(topKElements);

        // 构造返回字符串
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < topKElements.size(); i++) {
            if (i > 0) result.append(",");
            result.append(topKElements.get(i));
        }
        
        return result.toString();
    }

    public static void main(String[] args) {
        // 测试用例
        System.out.println(solution(new int[]{1, 1, 1, 2, 2, 3}, 2).equals("1,2"));
        System.out.println(solution(new int[]{1}, 1).equals("1"));
        System.out.println(solution(new int[]{4, 4, 4, 2, 2, 2, 3, 3, 1}, 2).equals("2,4"));
    }
}

具体流程

以 nums = [1, 1, 1, 2, 2, 3] 和 k = 2 为例:

统计频率:

  • 遍历数组 nums,统计每个元素的出现次数:
  • 1 出现 3 次
  • 2 出现 2 次
  • 3 出现 1 次
  • 得到哈希表 countMap: {1: 3, 2: 2, 3: 1}

找到前 k 个高频元素:

  • 使用优先队列 pq,按频率降序排列,如果频率相同则按值升序排列。
  • 将 countMap 中的所有条目添加到 pq 中。
  • 从 pq 中提取前 k 个元素(即频率最高的 k 个元素):
  • 提取第一个元素:1(频率 3)
  • 提取第二个元素:2(频率 2)

排序并返回结果: 对提取出的元素 [1, 2] 进行升序排列,将其转换为字符串格式 "1,2" 并返回。

总结分析

时间复杂度:统计频率:O(n),其中 n 是数组的长度。构建优先队列:O(n log k),因为我们需要将 n 个元素插入到大小为 k 的优先队列中。提取前 k 个元素为O(k log k)。排序为O(k log k)。总的时间复杂度为 O(n + n log k + k log k) ≈ O(n log k)。

空间复杂度:哈希表 countMap:O(n),用于存储每个元素及其频率。优先队列 pq:O(k),用于存储前 k 个高频元素。结果列表 topKElements:O(k),用于存储最终结果。总的空间复杂度为 O(n + k) ≈ O(n)。