青训营 X 豆包MarsCode 技术训练营 | 豆包MarsCode AI 刷题 | 69.查找热点数据问题

70 阅读3分钟

题目相关

问题描述

给你一个整数数组 nums 和一个整数 k,请你用一个字符串返回其中出现频率前 k 高的元素。请按升序排列。 你所设计算法的时间复杂度必须优于 O(nlogn),其中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"

解题

数据结构

对于统计前k个高频元素的问题,借卡哥的思路就是使用两种数据结构:

  • 哈希表
    • 用于统计每个元素的出现频率。哈希表的插入和查找操作的时间复杂度为 O(1),非常适合用于频率统计
  • 优先队列(底层就是堆实现的)
    • 用于维护前 k 个高频元素。优先队列可以在 O(nlog k) 的时间内插入和删除元素,非常适合用于动态维护前 k 个高频元素。而我们此处要使用的是小顶堆,因为每次移动更新堆的时候,每次弹出的元素是最大元素,就无法保留前k个高频元素,所以我们要用小顶堆,因为要统计最大前k个元素,只有小顶堆每次将最小的元素弹出,最后小顶堆里积累的才是前k个最大元素。
    • 引用卡哥的图(作者代码随想录) 小顶堆.png

算法思路

  1. 遍历数据nums,通过哈希表unorder_map将每个元素的出现频率作统计。
  2. 维护前k个高频的元素,在优先队列有元素插入时,如果队列的大小超过k,则会弹出队列中最小的元素,也就是频率最低的元素
  3. 构建结果数组result,从优先队列中弹出元素,依次将频率从高到低出现的元素依次填入结果数组result
  4. 排序结果数组,对数组result进行排序,确保元素按照升序排列
  5. 转化为字符串,通过stringstream将result转换为字符串,元素之间用逗号分隔

核心代码

  1. 统计频率并通过优先队列保存维护
unordered_map<int, int> map;
    for (int i = 0; i < nums.size(); i++){
        map[nums[i]]++;
    }
    priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparision> q;
    for (unordered_map<int,int>::iterator it = map.begin(); it != map.end(); it++){
        q.push(*it);
        if (q.size() > k){
            q.pop();
        }
    }

2. 构建小顶堆,通过对比pair中的第二个元素,也就是出现的频率进行排序

class mycomparision{
public:
    bool operator()(const pair<int, int>& lhs, pair<int, int>& rhs){
        return lhs.second > rhs.second;
    }
};

3. 将数组转换为字符串,通过使用stringstream将数组之前插入“,”分割,输出符合题目的格式

std::string vectorToString(const vector<int>& vec){
    stringstream ss;
    for (size_t i = 0; i < vec.size(); i++){
        if (i != 0){
            ss << ",";
        }
        ss << vec[i];
    }
    return ss.str();
}

全部代码

class mycomparision{
public:
    bool operator()(const pair<int, int>& lhs, pair<int, int>& rhs){
        return lhs.second > rhs.second;
    }
};


std::string vectorToString(const vector<int>& vec){
    stringstream ss;
    for (size_t i = 0; i < vec.size(); i++){
        if (i != 0){
            ss << ",";
        }
        ss << vec[i];
    }
    return ss.str();
}
std::string solution(std::vector<int> nums, int k) {
    // Please write your code here
    unordered_map<int, int> map;
    for (int i = 0; i < nums.size(); i++){
        map[nums[i]]++;
    }
    priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparision> q;
    for (unordered_map<int,int>::iterator it = map.begin(); it != map.end(); it++){
        q.push(*it);
        if (q.size() > k){
            q.pop();
        }
    }
    vector<int> result(q.size());
    for (int i = k - 1; i >= 0; i--){
        result[i] = q.top().first;
        q.pop();
    }
    sort(result.begin(), result.end());
    return vectorToString(result);
}

总结

通过使用哈希表和优先队列,我们可以在 O(n log k) 的时间复杂度、O(n)的空间复杂度内解决这个问题。哈希表用于快速统计元素的频率,优先队列用于动态维护前 k 个高频元素。最后,我们将结果数组排序并转换为字符串。