【分治-3】347.前K个高频元素

题目描述:

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

示例 1:

**输入:**nums = 1,1,1,2,2,3, k = 2

输出:1,2

示例 2:

**输入:**nums = 1, k = 1

输出:1

示例 3:

**输入:**nums = 1,2,1,2,1,2,3,1,3,2, k = 2

输出:1,2

解题思路:

方法一:哈希表 + 小根堆

核心思路:

  1. 哈希表统计频率 :unordered_map<int, int> 记录每个元素的出现次数

  2. 小根堆维护 Top K:遍历哈希表,用小根堆维护频率最高的 k 个元素

为什么用小根堆?

  • 堆里维护 k 个元素,堆顶是频率最低的

  • 新元素频率 > 堆顶频率 → 弹出堆顶,新元素入堆

  • 最终堆里就是频率最高的 k 个

具体过程示例:

nums = [1,1,1,2,2,3], k = 2

复制代码
第1步:统计频率
频率表: {1:3, 2:2, 3:1}

第2步:小根堆维护 Top K
遍历 1(频率3): 堆 [(3,1)]
遍历 2(频率2): 堆 [(2,2), (3,1)](堆顶频率2)
遍历 3(频率1): 1 < 堆顶2 → 不入堆

最终堆: [(2,2), (3,1)]
取出元素: [2, 1] ✅

代码实现:

cpp 复制代码
class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
        // 第1步:统计频率
        unordered_map<int, int> freq;
        for (int num : nums) {
            freq[num]++;
        }
        
        // 第2步:小根堆维护 Top K
        // pair<频率, 元素>
        priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
        
        for (auto& [num, count] : freq) {
            pq.push({count, num});
            if (pq.size() > k) {
                pq.pop();  // 弹出频率最小的
            }
        }
        
        // 第3步:取出结果
        vector<int> result;
        while (!pq.empty()) {
            result.push_back(pq.top().second);
            pq.pop();
        }
        
        return result;
    }
};

复杂度分析:

维度 复杂度 说明
时间复杂度 O(n log k) 统计频率 O(n) + 堆操作 O(m log k),m 是不同元素个数
空间复杂度 O(m) 哈希表 O(m) + 堆 O(k)

n 是数组长度,m 是不同元素个数,k 是要求的个数。

关键细节:

1. 为什么用 pair<int, int>?

priority_queue 默认按 pair 的 first 比较:

  • pair<频率, 元素>:按频率排序

  • greater<pair<int,int>>:小根堆,频率小的在堆顶

2. 为什么用 greater?
  • priority_queue 默认是大根堆(频率大的在堆顶)

  • 我们需要小根堆(频率小的在堆顶),这样才能淘汰频率最小的

3. 结构化绑定(C++17)
cpp 复制代码
for (auto& [num, count] : freq) {
    // num 是键,count 是值
}

等价于:

cpp 复制代码
for (auto& pair : freq) {
    int num = pair.first;
    int count = pair.second;
}

方法二:桶排序(O(n))

思路:频率最大为 n,用桶存储每个频率对应的元素。

代码实现:

cpp 复制代码
class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int, int> freq;
        for (int num : nums) freq[num]++;
        
        // 桶:下标是频率,值是元素列表
        int n = nums.size();
        vector<vector<int>> buckets(n + 1);
        for (auto& [num, count] : freq) {
            buckets[count].push_back(num);
        }
        
        // 从高频率往低频率取
        vector<int> result;
        for (int i = n; i >= 0 && result.size() < k; i--) {
            for (int num : buckets[i]) {
                result.push_back(num);
                if (result.size() == k) break;
            }
        }
        
        return result;
    }
};

复杂度:时间 O(n),空间 O(n)

优点:时间复杂度 O(n),比堆更快

缺点:需要额外 O(n) 空间

方法三:快速选择(O(n) 平均)

思路:把频率数组做快速选择,找第 k 大的频率。

复制代码
// 类似 215 题,但比较的是频率

复杂度:平均 O(n),最坏 O(n²)

三种方法对比:

方法 时间复杂度 空间复杂度 推荐度
哈希表 + 小根堆 O(n log k) O(m) ⭐⭐⭐⭐⭐
哈希表 + 桶排序 O(n) O(n) ⭐⭐⭐⭐
哈希表 + 快速选择 平均 O(n) O(m) ⭐⭐⭐⭐
相关推荐
头发够用的程序员1 小时前
TensorRT 自定义算子插件实战(一):从零手写 customScaledTanh
c++·人工智能·pytorch·python·深度学习·边缘计算·jetson
哭泣方源炼蛊1 小时前
正则表达式c++内容汇集
数据库·c++·mysql·正则表达式
索尔~古德曼1 小时前
CF(1400-1400&1300-1500)错题集
算法
别动我齐刘海1 小时前
简历技术栈全面复习——UDP / TCP / CAN / ZMQ / Protobuf 通信工程
网络·c++·python·tcp/ip·机器学习·udp·github
All for pursuit.2 小时前
【二分查找-2】34.在排序数组中查找元素的第一个和最后一个位置
数据结构·c++·算法·leetcode
泯泷3 小时前
为什么 AI 会"失忆"?——读懂 Agent 的记忆系统(一)
人工智能·算法·agent
大侠归来3 小时前
C语言与C++在基础语法上的区别
java·c语言·c++
看看我呀看看我3 小时前
基于Qt的虚拟键盘
c++·qt·qt5·qt6.3
bro_Java6663 小时前
链表进阶2:双向链表的构建
java·数据结构·链表