当前位置: 首页 > news >正文

LeetCode 热题 100_前 K 个高频元素(73_347_中等_C++)(堆)(哈希表+排序;哈希表+优先队列(小根堆))

LeetCode 热题 100_前 K 个高频元素(73_347)

    • 题目描述:
    • 输入输出样例:
    • 题解:
      • 解题思路:
        • 思路一(哈希表+排序):
        • 思路二(哈希表+优先队列(小根堆)):
      • 代码实现
        • 代码实现(思路一(哈希表+排序)):
        • 代码实现(思路二(哈希表+优先队列(小根堆))):
        • 以思路二为例进行调试
        • 部分代码解读

题目描述:

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

输入输出样例:

示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

示例 2:
输入: nums = [1], k = 1
输出: [1]

提示:
1 <= nums.length <= 105
k 的取值范围是 [1, 数组中不相同的元素的个数]
题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的

题解:

解题思路:

思路一(哈希表+排序):

1、创建一个哈希表,key来存储数组中元素,value存储对应key出现的次数。对value进行快速排序。提取排序后,前k个元素的值。

2、复杂度分析:
① 时间复杂度:O(nlogn),n为数组中元素的个数
    遍历 nums 数组中的每个元素,将其出现的频率存储到 unordered_map 中。这个过程的时间复杂度是 O(n)。
    将 unordered_map 的元素复制到 vector 中,这个操作的时间复杂度是 O(n)。
    对 tmp 中的元素进行排序,排序的时间复杂度是 O(n log n)。
    从排序后的 tmp 中选择前 k 个元素并将它们添加到 ans 中,这个过程的时间复杂度是 O(k)。
② 空间复杂度:O(n + log n),除返回答案的空间外,unordered_map 的空间复杂度是 O(n),vector<pair<int, int>> tmp 的空间复杂度是 O(n)。快速排序,空间复杂度是 O(log n)

思路二(哈希表+优先队列(小根堆)):

1、创建一个哈希表,key来存储数组中元素,value存储对应key出现的次数。创建可以维持 k 个元素的小根堆(根据value大小进行插入),这样在插入元素大于 k 时可以将堆顶的元素移出(优先级队列就是一个披着队列外衣的堆)。

2、复杂度分析
① 时间复杂度:O(n log k),其中 n 是输入数组的长度,k 是要求的前 k 个频率最高的元素。遍历 nums 数组并将每个元素的出现频率存入 unordered_map中为 O(n)。将元素插入最小堆O(n log k)。从堆中弹出元素并构建结果数组 O(k log k)。
② 空间复杂度: O(n + k),哈希表存储每个数字及其频率,最坏情况下需要存储所有 n 个数字 O(n)。最小堆的最大大小为 O(k)。

代码实现

代码实现(思路一(哈希表+排序)):
class Solution1 {
public:
    // 函数接受一个整数数组 nums 和一个整数 k, 返回出现频率最高的 k 个元素
    vector<int> topKFrequent(vector<int>& nums, int k) {
        // 使用 unordered_map 来统计每个数字出现的频率
        unordered_map<int,int> count;
        
        // 遍历 nums 数组,统计每个数字的出现次数
        for (int &num : nums){
            count[num]++; // 每遇到一个 num,就将对应的频率加 1
        }
        
        // 将 unordered_map 中的元素复制到一个 vector 中,以便进行排序
        vector<pair<int,int>> tmp(count.begin(), count.end());

        // 对 tmp 中的元素进行排序,按照频率降序排序
        // 使用 lambda 表达式作为排序规则
        sort(tmp.begin(), tmp.end(), [](pair<int,int>& a, pair<int,int>& b) { 
            return a.second > b.second; // 如果 a 的频率大于 b 的频率,返回 true 
        });
        
        // 创建一个结果 vector 来存放出现频率最高的 k 个元素
        vector<int> ans;
        
        // 选择排序后的前 k 个元素的第一个值(即数字)到 ans 中
        for (int i = 0; i < k; i++){
            ans.emplace_back(tmp[i].first); // 将 tmp 中的第 i 个元素的第一个值添加到 ans
        }

        return ans; // 返回结果
    }
};
代码实现(思路二(哈希表+优先队列(小根堆))):
class Solution2 {
private:
    // 自定义比较器类,用于堆的排序
    class Compare {
    public:
        // 重载比较运算符,按照频率(second)大小进行比较
        bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
            // 如果左边的频率大于右边,返回true。这样堆中频率小的元素会排在堆顶。
            return lhs.second > rhs.second;
        }
    };

public:
    // topKFrequent 函数返回数组中出现频率前k大的元素
    vector<int> topKFrequent(vector<int>& nums, int k) {
        // 使用 unordered_map 来存储每个元素及其出现的频率
        unordered_map<int, int> count;

        // 统计数组中每个元素的出现次数
        for (int i = 0; i < nums.size(); i++) {
            count[nums[i]]++;
        }

        // 创建一个最小堆来保存频率前 k 大的元素
        // priority_queue 的第三个参数是自定义的比较器 Compare,确保堆顶是频率最小的元素
        priority_queue<pair<int, int>, vector<pair<int, int>>, Compare> min_head;

        // 遍历计数哈希表,将每个元素(和其频率)加入到堆中
        for (auto& i : count) {
            min_head.push(i); // 将元素及其频率压入堆
            // 如果堆的大小超过了 k,则弹出堆顶元素(频率最小的元素)
            if (min_head.size() > k) {
                min_head.pop();
            }
        }

        // 用来存储最终的前 k 个频率最大的元素
        vector<int> ans(k);

        // 从堆中依次弹出元素,存入答案数组
        // 由于堆顶是频率最小的元素,因此我们从堆顶弹出并将元素存入结果数组
        for (int i = k - 1; i >= 0; i--) {
            ans[i] = min_head.top().first; // 获取堆顶元素的值(即数字)
            min_head.pop(); // 弹出堆顶元素
        }

        return ans; // 返回包含频率前 k 大的元素的数组
    }
};
以思路二为例进行调试
#include<iostream>
#include <vector>
#include<unordered_map>
#include<algorithm>
#include <queue>
using namespace std;

class Solution2 {
private:
    // 自定义比较器类,用于堆的排序
    class Compare {
    public:
        // 重载比较运算符,按照频率(second)大小进行比较
        bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
            // 如果左边的频率大于右边,返回true。这样堆中频率小的元素会排在堆顶。
            return lhs.second > rhs.second;
        }
    };

public:
    // topKFrequent 函数返回数组中出现频率前k大的元素
    vector<int> topKFrequent(vector<int>& nums, int k) {
        // 使用 unordered_map 来存储每个元素及其出现的频率
        unordered_map<int, int> count;

        // 统计数组中每个元素的出现次数
        for (int i = 0; i < nums.size(); i++) {
            count[nums[i]]++;
        }

        // 创建一个最小堆来保存频率前 k 大的元素
        // priority_queue 的第三个参数是自定义的比较器 Compare,确保堆顶是频率最小的元素
        priority_queue<pair<int, int>, vector<pair<int, int>>, Compare> min_head;

        // 遍历计数哈希表,将每个元素(和其频率)加入到堆中
        for (auto& i : count) {
            min_head.push(i); // 将元素及其频率压入堆
            // 如果堆的大小超过了 k,则弹出堆顶元素(频率最小的元素)
            if (min_head.size() > k) {
                min_head.pop();
            }
        }

        // 用来存储最终的前 k 个频率最大的元素
        vector<int> ans(k);

        // 从堆中依次弹出元素,存入答案数组
        // 由于堆顶是频率最小的元素,因此我们从堆顶弹出并将元素存入结果数组
        for (int i = k - 1; i >= 0; i--) {
            ans[i] = min_head.top().first; // 获取堆顶元素的值(即数字)
            min_head.pop(); // 弹出堆顶元素
        }

        return ans; // 返回包含频率前 k 大的元素的数组
    }
};

int main(int argc, char const *argv[])
{
    vector<int> nums={1,1,1,2,2,3};
    int k=2;
    Solution2 s2;
    vector<int> ans=s2.topKFrequent(nums,k);
    cout<<"[";
    for (int i = 0; i < ans.size(); i++){
        cout<<ans[i];
        if (i!=ans.size()-1){
            cout<<",";
        }
        
    }
    
    cout<<"]";
    return 0;
}

部分代码解读

比较器是 lambda 表达式

sort(tmp.begin(), tmp.end(), [](pair<int,int>& a, pair<int,int>& b) { 
    return a.second > b.second; // 如果 a 的频率大于 b 的频率,返回 true 
});

最常见的比较器是 lambda 表达式,因为它:
非常简洁,适用于大多数场景,特别是排序、查找等常用算法。
可以在需要时动态定义比较逻辑,而不需要额外定义函数或类。

Compare:自定义比较器类 (Functor)

class Compare {
public:
    // 重载比较运算符,按照频率(second)大小进行比较
    bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
        // 如果左边的频率大于右边,返回true。这样堆中频率小的元素会排在堆顶。
        return lhs.second > rhs.second;
    }
};
priority_queue<pair<int, int>, vector<pair<int, int>>, Compare> min_head;

Compare:自定义比较器类 (Functor)
pair<int, int> 是一个由两个 int 类型元素组成的模板类,它可以存储一对值。
这里的 vector<pair<int, int>> 表示堆的底层容器是一个 pair<int, int> 类型的 vector。
优点:
复用性强:可以在多个地方使用相同的比较逻辑,不需要每次都重新定义。
性能较好:在某些情况下,函数对象(类)可能比 lambda 表达式稍微高效,因为它可以在编译时优化。
适合复杂逻辑:当比较逻辑较复杂时,使用类比 lambda 更加清晰和易于管理。
缺点:
代码较多:需要定义一个额外的类,增加了代码的复杂性,尤其是对于简单的比较。

LeetCode 热题 100_前 K 个高频元素(73_347)原题链接
欢迎大家和我沟通交流(✿◠‿◠)

相关文章:

  • buu-ciscn_2019_ne_5-好久不见50
  • 学习threejs,使用MeshFaceMaterial面材质容器
  • Java泛型程序设计使用方法
  • 探索 C 语言枚举类型的奇妙世界
  • 【NLP 37、实践 ⑨ NER 命名实体识别任务 LSTM + CRF 实现】
  • Language Models are Few-Shot Learners,GPT-3详细讲解
  • petalinxu 在zynq的FPGA下的ST7735S的驱动配置
  • 射频辐射干扰:变频器电缆的电磁天线效应
  • 9-1 USART串口协议
  • C语言高级进阶4
  • WinSW-x64(2.12.0)将nginx注册为服务可能有bug
  • 【区块链】btc
  • C语言 第四章 数组(4)
  • scanf() 函数:C语言中的数据输入桥梁
  • SAP FI模块之付款管理开发
  • 理解langchain langgraph 官方文档示例代码中的MemorySaver
  • 稀疏矩阵(信息学奥赛一本通-2042)
  • 芯谷D8563TS实时时钟/日历芯片详解可替代PCF8563
  • notion enhancer 新版工作方法
  • torch_geometric 安装
  • 焦点访谈丨售假手段又翻新,警惕化肥“忽悠团”的坑农套路
  • 上影节官方海报公布:电影之城,每一帧都是生活
  • 山西晋城一网红徒步野游线路据传发生驴友坠崖,当地已宣布封路
  • 商务部就美国商务部调整芯片出口管制有关表述答记者问
  • 外交部发言人就第78届世界卫生大会拒绝涉台提案发表谈话
  • 聚焦中华文明精神标识,多校专家学者跨学科对话交流