《代码随想录》第五章 栈与队列 347. 前 K 个高频元素

《代码随想录》第五章 栈与队列 347. 前 K 个高频元素

努力学习!

题目:力扣链接

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

一、思想

这道题的核心思想是统计频率 + 动态维护前 k 个高频元素

  1. 统计频率

    • 使用哈希表(unordered_map​)统计每个元素出现的频率。
    • 哈希表的查找、插入、删除操作的平均时间复杂度为 O(1),适合高效统计频率。
  2. 动态维护前 k 个高频元素

    • 使用优先队列(最小堆)来动态维护当前频率最高的 k​ 个元素。

      • 最大堆的堆顶是最大元素,无法直接移除最小频率的元素。
    • 优先队列的特点是队首元素是最小值,因此可以通过不断替换最小频率的元素,确保队列中始终是频率最高的 k​ 个元素。

  3. 提取结果

    • 最后从优先队列中提取出频率最高的 k​ 个元素,按从高到低的顺序存入结果数组。

二、代码

class Solution
{
public:
    /**
     * 内部类mycomparison,重载()运算符,用于比较pair的第二个元素的大小
     * 这是一个函数对象(functor),用于定义priority_queue的比较规则
     * 使用大于号(>)实现最小堆,即频率较小的元素在堆顶
     */
    class mycomparison
    {
    public:
        bool operator()(const pair<int, int> &lhs, const pair<int, int> &rhs) { return lhs.second > rhs.second; }
    };
    
    /**
     * 函数topKFrequent,用于找出数组中出现频率最高的k个元素
     * 时间复杂度:O(n log k),其中n是数组长度
     * 空间复杂度:O(n),用于存储哈希表和优先队列
     * @param nums 输入的整数数组
     * @param k 需要找出的元素个数
     * @return 返回出现频率最高的k个元素的数组
     */
    vector<int> topKFrequent(vector<int> &nums, int k)
    {
        // 创建一个哈希表,存储每个元素出现的频率
        // unordered_map使用哈希表实现,查找时间复杂度为O(1)
        unordered_map<int, int> map;
        for (int i = 0; i < nums.size(); ++i) {
            map[nums[i]]++;  // 统计每个元素出现的次数
        }

        // 创建一个优先队列,按照元素的频率进行排序
        // 使用最小堆,只保留频率最高的k个元素
        // 优先队列的模板参数:
        // 1. 存储的元素类型:pair<int, int>(元素值,频率)
        // 2. 底层容器:vector<pair<int, int>>
        // 3. 比较函数:mycomparison
        priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;
        
        // 使用迭代器遍历哈希表
        for (unordered_map<int, int>::iterator it = map.begin(); it != map.end(); ++it) {
            pri_que.push(*it);  // 将元素插入优先队列
            // 如果优先队列的大小超过k,则弹出频率最低的元素
            if (pri_que.size() > k) {
                pri_que.pop();  // 移除堆顶元素(当前最小频率)
            }
        }

        // 创建一个数组,存储出现频率最高的k个元素
        vector<int> res(k);
        // 从后往前填充结果数组,因为优先队列是最小堆,堆顶是最小频率
        for (int i = k - 1; i >= 0; --i) {
            res[i] = pri_que.top().first;  // 获取当前最大频率元素
            pri_que.pop();  // 移除堆顶元素
        }
        return res;
    }
};

三、代码解析

1. 算法工作原理解析及代码分析

1.1 统计频率

unordered_map<int, int> map;
for (int i = 0; i < nums.size(); ++i) {
    map[nums[i]]++;  // 统计每个元素出现的次数
}
  • 目的:统计数组中每个元素出现的频率。

  • 实现

    • 使用 unordered_map​,键为数组元素的值,值为元素出现的次数。
    • 遍历数组,对每个元素 nums[i]​,在哈希表中对应的值加 1。

1.2 构建优先队列(最小堆)

priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;
  • 目的:动态维护当前频率最高的 k​ 个元素。

  • 实现

    • 使用 priority_queue​,存储元素类型为 pair<int, int>​(元素值,频率)。
    • 自定义比较函数 mycomparison​,使用 >​ 实现最小堆。

    时间复杂度:每次插入和删除操作的时间复杂度为 O(log k)。

1.3 动态维护前 k 个高频元素

for (unordered_map<int, int>::iterator it = map.begin(); it != map.end(); ++it) {
    pri_que.push(*it);  // 将元素插入优先队列
    if (pri_que.size() > k) {
        pri_que.pop();  // 移除堆顶元素(当前最小频率)
    }
}
  • 目的:确保优先队列中始终是频率最高的 k​ 个元素。

  • 实现

    • 遍历哈希表,将每个键值对插入优先队列。
    • 如果队列大小超过 k​,移除堆顶元素(当前最小频率)。
  • 时间复杂度:O(n log k),其中 n​ 是数组长度,k​ 是目标元素个数。

2. 关键点说明

2.1 哈希表的统计频率

  • 关键点:哈希表的高效查找和插入操作。

  • 细节

    • 使用 unordered_map​,平均时间复杂度为 O(1)。
    • 通过 map[nums[i]]++​ 简洁高效地统计频率。

2.2 优先队列(最小堆)的设计

  • 关键点:最小堆的动态维护。

  • 细节

    • 优先队列的模板参数:

      • 存储元素类型pair<int, int>​。
      • 底层容器vector<pair<int, int>>​。
      • 比较函数mycomparison​,使用 >​ 实现最小堆。
    • 最小堆的特点是堆顶元素是最小值,确保队列中始终是频率最高的 k​ 个元素。

2.3 动态维护前 k 个高频元素

  • 关键点:优先队列的大小控制。

  • 细节

    • 每次插入新元素后,检查队列大小是否超过 k​。
    • 如果超过 k​,移除堆顶元素(当前最小频率),确保队列中始终是频率最高的 k​ 个元素。

2.4 提取结果的顺序

  • 关键点:按频率从高到低的顺序提取元素。

  • 细节

    • 优先队列是最小堆,堆顶是最小频率元素。
    • 从后往前填充结果数组,确保结果按频率从高到低排列。

四、复杂度分析

  • 时间复杂度

    • 统计频率:O(n),其中 n​ 是数组长度。
    • 优先队列操作:每次插入和删除的时间复杂度是 O(log k),总共有 n​ 次操作,因此总时间复杂度为 O(n log k)。
    • 总体时间复杂度:O(n log k)。
  • 空间复杂度

    • 哈希表:O(n),存储所有元素的频率。
    • 优先队列:O(k),存储前 k​ 个高频元素。
    • 总体空间复杂度:O(n)。

白展堂:人生就是这样,苦和累你总得选一样吧?哪有什么好事都让你一个人占了呢。 ——《武林外传》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值