《代码随想录》第五章 栈与队列 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)。

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

先展示下效果 https://pan.quark.cn/s/a4b39357ea24 遗传算法 - 简书 遗传算法的理论是根据达尔文进化论而设计出来的算法: 人类是朝着好的方向(最优解)进化,进化过程中,会自动选择优良基因,淘汰劣等基因。 遗传算法(英语:genetic algorithm (GA) )是计算数学中用于解决最佳化的搜索算法,是进化算法的一种。 进化算法最初是借鉴了进化生物学中的一些现象而发展起来的,这些现象包括遗传、突变、自然选择、杂交等。 搜索算法的共同特征为: 首先组成一组候选解 依据某些适应性条件测算这些候选解的适应度 根据适应度保留某些候选解,放弃其他候选解 对保留的候选解进行某些操作,生成新的候选解 遗传算法流程 遗传算法的一般步骤 my_fitness函数 评估每条染色体所对应个体的适应度 升序排列适应度评估值,选出 parent_number 个 个体作为 待选 parent 种群(适应度函数的值越小越好) 从 待选 parent 种群 中随机选择 2 个个体作为父方和母方。 抽取父母双方的染色体,进行交叉,产生 2 个子代。 (交叉概率) 对子代(parent + 生成的 child)的染色体进行变异。 (变异概率) 重复3,4,5步骤,直到新种群(parentnumber + childnumber)的产生。 循环以上步骤直至找到满意的解。 名词解释 交叉概率:两个个体进行交配的概率。 例如,交配概率为0.8,则80%的“夫妻”会生育后代。 变异概率:所有的基因中发生变异的占总体的比例。 GA函数 适应度函数 适应度函数由解决的问题决定。 举一个平方和的例子。 简单的平方和问题 求函数的最小值,其中每个变量的取值区间都是 [-1, ...
### 关于队列的数据结构解析 #### 双端队列 (Deque) 双端队列是一种特殊的数据结构,允许在两端高效地执行插入和删除操作。Python 中的 `collections.deque` 提供了这种功能,其设计使得它非常适合处理需要频繁在一端或两端进行元素增删的操作场景[^1]。 以下是 `deque` 的一些常用方法及其作用: - **append(x)**: 将元素 x 添加到右端。 - **appendleft(x)**: 将元素 x 添加到左端。 - **pop()**: 移除并返回右端的元素。 - **popleft()**: 移除并返回左端的元素。 下面是一个简单的代码示例展示如何使用 `deque`: ```python from collections import deque dq = deque() dq.append(1) # 在右侧添加元素 1 dq.appendleft(2) # 在左侧添加元素 2 print(dq.popleft()) # 输出 2 并将其从左侧移除 print(dq.pop()) # 输出 1 并将其从右侧移除 ``` #### 队列 (Queue) 队列是一种遵循 FIFO(First In First Out,先进先出)原则的数据结构。标准库中的 STL 队列并不提供迭代器支持,因此无法像列表那样直接遍历其中的元素[^3]。然而,在实际编程中,我们通常会借助其他工具来实现更灵活的功能。 对于 C++ 而言,虽然 STL 队列不具备可迭代特性,但它可以通过底层容器(如 vector 或 list)间接访问内部存储的内容。而在 Python 中,则可以利用 `queue.Queue` 类或者继续沿用面提到过的 `deque` 来模拟传统意义上的队列行为。 #### 映射表 (Map/Hash Map) 映射表也是一种重要的数据结构形式之一,主要用于建立键值对之间的关联关系。当面对查找频率较高的需求时尤为适用。例如在一个给定数组里统计各个数值出现次数的任务就可以通过哈希映射轻松解决[^2]。 这里给出一段基于 map 统计整数数组各元素频次的例子: ```cpp #include <iostream> #include <unordered_map> int main(){ int nums[] = {1, 2, 3, 2, 4}; std::unordered_map<int,int> freq; for(auto num : nums){ freq[num]++; } for(const auto& pair : freq){ std::cout << "Number:" << pair.first << ", Frequency:"<<pair.second<<std::endl; } } ``` 以上程序片段展示了如何运用 unordered_map 对一组数字进行快速有效的频率分析。 --- ### 总结 无论是作为基础构建模块还是高级算法的核心组件,理解这些基本概念以及它们之间相互转换的能力都是非常必要的。希望上述解释能够帮助您更好地掌握有关队列以及其他相关联的知识点!
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值