ch6_8 数组中前K个高频元素

1.1 涵盖知识点

  1. 完全二叉树定义,

  2. 满足 堆序型的完全二叉树 形成 :堆;

  3. 使用 优先级队列容器适配器 实现堆 ;

  4. 优先级队列:优先级队列中元素的出队顺序 与元素的优先级有关。

  5. 优先级队列中, 三个参数的意义:


template <typename T,
        typename Container=std::vector<T>,
        typename Compare=std::less<T> >
class priority_queue{
    //......
}

1. typename T:指定存储元素的具体类型;

2. typename Container:指定 priority_queue 底层使用的基础容器,

3. typename Compare:指定容器中评定元素优先级所遵循的排序规则,
默认使用std::less<T>按照元素值从大到小进行排序,
还可以使用std::greater<T>按照元素值从小到大排序,
但更多情况下是使用自定义的排序规则。

上述的 数据结构, 在 C++ STL 标准模版库中, 使用了 priority_queue: 优先级队列容器适配器实现了;

  1. pair<int, int>: 关联式容器 中的 pair 类模板: 用来将2个普通元素 first, second 创建成一个新的元素,称为键值对 <key, value> ;

  2. unordered_map: 关联式容器, 其中存储的元素, 都是一个一个的 “键值对” (<key, value>);
    并且 unordered_map 底层实现是一个哈希表,key 无序,且不可重复, key 不可以修改;

  3. 容器中元素的遍历, 这里是 unordered_map 容器, 采用 增强型的 for 循环方式;

  4. auto: 自动类型推导;

  5. decltype: 声明类型, declare type;

  6. 向量容器中的 emplace_back() 方法: 在序列尾部生成一个元素;

  7. priority_queue 优先级队列 容器适配器: 中的 emplace() 方法: 此方法的作用, 根据既定的排序规则,在容器适配器适当的位置直接 生成该 新元素;
    (emplace(Args&&… args),而对于 类对象来说, 可能需要多个数据构造出一个对象, 所以使用 Agrs … args 表示构造一个存储类型的元素,所需要的数据)

2 逻辑步骤

构建一个比较函数, 返回bool 类型; 用于比较两对<key, value> 中, 谁的 value 大, 即对应元素出现的次数更多;

构建 题目任务的 topK 函数, 返回一个向量容器, 其中存储的元素 是前 K 个 高频出现的 元素值;

  1. 新建一个 无序的关联式容器 unordered_map<> uMap, 用于存放,数组元素以及 对应元素出现的次数;

  2. 遍历给定的数组, 将给定数组中的元素作为key 存储在 uMap 中, 该元素出现的次数作为 value 存储在 uMap 中;

  3. 使用优先级队列,构建一个小根堆; 注意priority_que 传入的 三个参数的意义, 使用该优先级队列 按照第三个传入的参数 规则, 形成一个小顶堆;

  4. 遍历 uMap 中 <key, value>, 将其中元素存入到小根堆中,并作维护大小为 K 的 小顶堆;
    4.1 如果堆的大小 == K 个 键值对:
    并且此时如果, 小根堆的堆顶中的键值对,<key, value>, value 小于当前 uMap 中的 freq, 则将顶层的 <key, value> 移除,将当前的uMap 中的 <ele, freq> 加入到队列中;
    4.2 否则, 堆的大小小于 K 时, 则直接将当前uMap 中的<key, value> 加入堆中;

  5. 创建一个结果集 向量容器 ret; 用于存储小根堆中 每一对<key, value> 中的 key;

  6. 遍历小根堆, 当小根堆不为空时, 将小根堆中每一对 <key, value> 中的 key , 存入到 ret 中, 并移除当前堆顶的 <key, value>.

  7. 返回 ret;

#include "unordered_map" //  用于保存数组的元素, 以及元素出现的次数;   其中包含了 pair<key, value> 的模版
#include "vector"   // 用于保存最终top k 个元素;
#include "queue"   //  调用 priority_queue 构建 小根堆;

using namespace std;

class  Solution{
public:
    //  制定优先级队列中, 优先级的规则定义;  即判断两个元素 对应的频率更大;
    static bool  cmp_fun(pair<int,int>& m, pair<int, int>& n )  {
        return  m.second > n.second;  // 按照 value 的大小作为 优先级 标注;
    }


    vector<int>  topKFreq(vector<int>& nums, int k){
        // 1. 新建一个 unordered_map 用于存放元素和次数;
        unordered_map<int, int>  uMap;

        //  2. 遍历数组, 将其中的元素, 以及对应次数存入到 uMap 中;
        for(auto ele: nums)  uMap[ele]++;  // <key, value>: key 代表 数组中的元素, value: 代表该元素出现的次数;


        // 3.  使用优先级队列, 构造小根堆; 注意优先级队列三个参数的意义;
        priority_queue<pair<int, int>,  vector<pair<int, int>>,  decltype(&cmp_fun) >   heap(cmp_fun);


        // 4.  遍历uMap 将其中的 <key, value> , 形成一个 大小为K 的小根堆;;
        for(auto& [ele, freq]: uMap){
            if(heap.size() ==  k){  //  如果小根堆的大小为 K, 则此时开始 维护 小根堆;
                if(heap.top().second < freq){  // 如果堆顶的键值对中的 freq 小于当前的 freq, 则移除堆顶的 键值对, 存入当前的键值对;
                    heap.pop();
                    heap.emplace(ele, freq);  // 使用 emplace()   优先级队列中的方法;
                }
            }else{ heap.emplace( ele, freq);}  // 否则,堆的大小 小于 K, 直接存入键值对;

        }

        // 5. 创建一个结果集, 用于保存小根堆中 的 key 数值;
        vector<int>  result;
        // 6.  当小根堆不为空时, 将其中每一对的 key 存入到结果集中;
        while (!heap.empty()){
            result.push_back(heap.top().first);
            heap.pop();
        }
        return  result;
    }

};

// 时间复杂度:O(nlogk)
// 空间复杂度:O(n)
class Solution {
public:
    // 小顶堆
    class mycomparison {
    public:
        bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
            return lhs.second > rhs.second;
        }
    };
    vector<int> topKFrequent(vector<int>& nums, int k) {
        // 要统计元素出现频率
        unordered_map<int, int> map; // map<nums[i],对应出现的次数>
        for (int i = 0; i < nums.size(); i++) {
            map[nums[i]]++;
        }

        // 对频率排序
        // 定义一个小顶堆,大小为k
        priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;

        // 用固定大小为k的小顶堆,扫面所有频率的数值
        for (unordered_map<int, int>::iterator it = map.begin(); it != map.end(); it++) {
            pri_que.push(*it);
            if (pri_que.size() > k) { // 如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
                pri_que.pop();
            }
        }

        // 找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
        vector<int> result(k);
        for (int i = k - 1; i >= 0; i--) {
            result[i] = pri_que.top().first;
            pri_que.pop();
        }
        return result;

    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值