C++ 优先级队列 大小堆 模拟 力扣 703. 数据流中的第 K 大元素 每日一题

文章目录

一、题目描述

题目链接:力扣 703. 数据流中的第 K 大元素

题目描述:

示例 1:

输入:

"KthLargest", "add", "add", "add", "add", "add"

\[3, \[4, 5, 8, 2\]\], \[3\], \[5\], \[10\], \[9\], \[4\]

输出:

null, 4, 5, 5, 8, 8

解释:

KthLargest kthLargest = new KthLargest(3, [4,5,8,2]);

kthLargest.add(3); // 返回 4

kthLargest.add(5); // 返回 5

kthLargest.add(10); // 返回 5

kthLargest.add(9); // 返回 8

kthLargest.add(4); // 返回 8
提示:

1 <= k <= 10⁴

0 <= nums.length <= 10⁴

-10⁴ <= nums[i] <= 10⁴

-10⁴ <= val <= 10⁴

最多调用 add 方法 10⁴ 次

题目数据保证,在查找第 k 大元素时,数据流中至少有 k 个元素

二、为什么这道题值得我们花几分钟弄懂?

这道题是堆(优先级队列)进阶应用的经典题,是面试中考察堆灵活使用的高频题。它和上一题"最后一块石头的重量"形成互补,从"大顶堆的基础使用"过渡到"小顶堆的场景化应用",能帮我们真正理解"根据场景选择堆类型"的核心思维,是夯实堆结构应用能力的关键题。

题目核心价值:

  • 堆类型的灵活选择:本题需要"维护第k大元素",小顶堆是最优解,能让你理解"不同堆类型适配不同场景"的底层逻辑,而非只会机械使用大顶堆。
  • 空间优化思维:用大小为k的小顶堆,将空间复杂度从O(n)优化到O(k),体现"用数据结构特性做空间优化"的工程思维。
  • 数据流处理能力:本题模拟"动态数据流"场景(数据不断新增),是实际开发中日志分析、实时统计等场景的简化版,能训练你处理动态数据的能力。
  • 面试的"进阶筛选题":相比基础堆题,本题更考察对堆的理解深度 而非使用熟练度,能区分"会用堆"和"懂堆的应用场景"的候选人。

面试考察的核心方向:

  1. 堆类型的选择逻辑:能否意识到"维护第k大元素"用小顶堆最优,而非暴力排序或大顶堆。
  2. 小顶堆的使用能力:能否熟练配置C++ priority_queue 为小顶堆(指定比较器)。
  3. 空间优化思路:能否解释"为什么只用大小为k的小顶堆就能解决问题",而非存储所有元素。
  4. 复杂度分析:能否准确分析初始化和add操作的时间复杂度,理解小顶堆相比暴力排序的效率优势。

三、算法原理

小顶堆的核心特性

小顶堆是优先级队列的一种,其核心特性是堆顶元素是整个堆的最小值,且核心操作(插入、删除堆顶)的时间复杂度仍为O(logk)(k为堆的大小):

  • 插入元素:O(logk)
  • 删除堆顶元素:O(logk)
  • 获取堆顶元素:O(1)

在C++中,标准库的priority_queue默认是大顶堆,要实现小顶堆需要显式指定比较器:

cpp 复制代码
// 小顶堆的定义方式:三个模板参数分别是元素类型、底层容器、比较器
priority_queue<int, vector<int>, greater<int>> min_heap;

这道题的核心算法是 "大小为k的小顶堆 + 动态维护":用小顶堆存储数据流中"当前最大的k个元素",堆顶就是这k个元素的最小值,也就是整个数据流的第k大元素。

  1. 初始化阶段
    • 创建一个大小为k的小顶堆。
    • 遍历初始数组nums,将每个元素插入堆中。
    • 若堆的大小超过k,删除堆顶元素(最小值),确保堆中始终只保留最大的k个元素。
  2. add操作阶段
    • 将新元素val插入堆中。
    • 若堆的大小超过k,删除堆顶元素。
    • 此时堆顶元素就是当前数据流的第k大元素,直接返回。

这个思路的本质是:用小顶堆"过滤"掉数据流中较小的元素,只保留最大的k个,堆顶就是我们要找的第k大元素

细节注意

  1. 堆类型选择:本题用的是小顶堆,若用大顶堆则需要存储所有元素,空间复杂度更高,且获取第k大元素需要弹出k次,效率更低。
  2. 堆大小控制:初始化和add操作中,必须在插入后检查堆大小,超过k就删除堆顶,这是保证堆顶是第k大元素的核心。
  3. 边界条件:
    • 初始数组nums为空时,堆初始为空,后续add操作会逐步填充到k个元素。
    • 题目保证调用add时数据流至少有k个元素,无需处理堆大小不足k的情况。
  4. 面试重点:能解释"为什么小顶堆+固定大小k"是最优解,而非只会写代码------这是面试中区分理解深度的关键。

四、代码实现

cpp 复制代码
#include <vector>
#include <queue>
using namespace std;

class KthLargest {
public:
    // 构造函数:初始化k和小顶堆
    KthLargest(int k, vector<int>& nums)
    {
        _size = k; // 保存第k大的k值
        // 遍历初始数组,填充小顶堆
        for(auto e : nums)
        {
            _q.push(e); // 插入当前元素
            // 若堆大小超过k,删除堆顶(最小值),保证堆中只保留最大的k个元素
            if(_q.size() > _size)
                _q.pop();
        }
    }
    
    // 添加新元素,并返回当前第k大的元素
    int add(int val) 
    {
        _q.push(val); // 插入新元素
        // 维护堆的大小为k
        if(_q.size() > _size)
            _q.pop();
        // 堆顶就是第k大的元素
        return _q.top();
    }
private:
    // 小顶堆:greater<int>指定比较器,堆顶为最小值
    priority_queue<int, vector<int>, greater<int>> _q;
    int _size; // 保存k值,代表要找的是第k大元素
};

/**
 * Your KthLargest object will be instantiated and called as such:
 * KthLargest* obj = new KthLargest(k, nums);
 * int param_1 = obj->add(val);
 */

代码细节说明

  1. 小顶堆定义
    • priority_queue<int, vector<int>, greater<int>> _q; 是C++中小顶堆的标准定义方式:
      • 第一个参数:元素类型(int);
      • 第二个参数:底层存储容器(vector);
      • 第三个参数:比较器(greater 表示"大于",即小的元素优先级更高,形成小顶堆)。
  2. 构造函数逻辑
    • 先保存k值到成员变量_size,避免后续重复传参;
    • 遍历初始数组,逐个插入堆中,同时保证堆大小不超过k,核心目的是"筛选出初始数组中最大的k个元素"。
  3. add函数逻辑
    • 插入新元素后,同样维护堆大小为k;
    • 直接返回堆顶,因为此时堆顶是"当前最大的k个元素中的最小值",也就是整个数据流的第k大元素。

复杂度分析

  • 初始化时间复杂度 :O(nlogk)。n是初始数组nums的长度,每个元素插入堆的时间是O(logk)(堆大小最多为k),因此整体为O(nlogk)。
  • add操作时间复杂度:O(logk)。每次add仅插入一个元素,堆操作的时间复杂度为O(logk)。
  • 空间复杂度:O(k)。堆中最多存储k个元素,空间复杂度为O(k),相比存储所有元素的O(n)大幅优化。

五、下题预告

下一篇我们一起学习堆的综合应用,攻克 692. 前K个高频单词。这道题会在我们今天的堆(优先级队列)的基础上加入哈希表进行映射更加综合~

搞定这道题目,你真的真的很棒哦!如果对小顶堆的定义、堆大小维护的逻辑还有疑问,或者有更简洁的实现思路,都可以在评论区交流~

别忘了点个赞、关个注~(๑˃̵ᴗ˂̵)و 你的支持就是继续肝优质算法内容的最大动力~我们下道题,不见不散~

相关推荐
木井巳7 小时前
【递归算法】二叉搜索树中第K小的元素
java·算法·leetcode·深度优先·剪枝
铉铉这波能秀7 小时前
LeetCode Hot100 中 enumerate 函数的妙用(2026.2月版)
数据结构·python·算法·leetcode·职场和发展·开发
墨有6667 小时前
哈希表从入门到实现,一篇吃透!
数据结构·算法·哈希算法
Yu_Lijing7 小时前
网络复习篇——网络基础(一)
网络·c++·笔记
Bella的成长园地7 小时前
为什么c++中的条件变量的 wait() 函数需要配合while 循环或谓词?
c++·面试
We་ct7 小时前
LeetCode 228. 汇总区间:解题思路+代码详解
前端·算法·leetcode·typescript
charlee447 小时前
为什么现代 C++ 库都用 PIMPL?一场关于封装、依赖与安全的演进
c++·智能指针·raii·pimpl·编译防火墙·封装设计
AIpanda8887 小时前
如何借助AI销冠系统提升数字员工在销售中的成效?
算法
啊阿狸不会拉杆7 小时前
《机器学习导论》第 7 章-聚类
数据结构·人工智能·python·算法·机器学习·数据挖掘·聚类