news 2026/6/26 10:41:09

算法(单调队列、优先队列)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
算法(单调队列、优先队列)

引言

239. 滑动窗口最大值 - 力扣(LeetCode)

347. 前 K 个高频元素 - 力扣(LeetCode)

第一题

这一题我们将引入一个单调队列,可能大家对于单调栈并不陌生,但是单调队列的题目可能接触的比较少。单调队列解决的问题就是维护一个窗口的数据,类似于大顶堆的作用,但是并不是大顶堆。比如说大顶堆(优先队列),对于1,3,-1来说,就变成了3,1,-1,但是我们可以发现,当我们窗口移动的时候,我们需要把1弹出去,但是无论我们是从头还是尾,都无法弹出1,所以我们需要自己构造一个单调队列。我们这个队列里面只需要维护最大的元素,还是1,3,-1,当我们3进入之后,1就会自动的被我们弹出去,因为我们要求的是最大的元素,既然已经有3进来了,那么1的存在已经没有任何的意义了,所以只要有比前面大的元素进来,那么就会自动把前面那些比这个元素小的元素卷走,不需要我们在主函数里面手动调用pop()函数。这样子,我们就可以永远保证队头的元素是最大值,并且从队头到队尾,元素的大小依次减小。所以每次我们需要知道这个队列里面最大值是多少的时候,就访问队头元素即可。

我们这个队列是双端队列,也就是用deque实现的,而我们的队头是出队列的,队尾是入队列的,pop的含义就是每一次移动窗口的时候把多余的那一个元素从队头弹出去(注意:这个操作并不包含我们加入一个比之前的大的元素然后把前面那些元素弹走,原理就是我们只需要维护这个队列里面最大的元素,一定要把最大的元素放在队头)

push()函数就是加入元素,不过在加入元素之前要比较末尾的元素,为什么是末尾的元素呢?因为我们的队列是递减的,比如5,3要加入4,那么如果比较队头,就无法把3出队,而比较队尾,就是把3出队,然后把4入队,变成5,4

最后在主函数里面,我们先把队列里面的元素全部初始化好,然后窗口开始往后面移动

至于我们为什么要值传递,是因为方便我们直接操作元素,用nums[i-k]

class MyQueue { public: deque<int> que; void pop(int val) { if (!que.empty() && val == que.front()) { que.pop_front(); } } void push(int val) { while (!que.empty() && val > que.back()) { que.pop_back(); } que.push_back(val); } int getMaxValue () { return que.front(); } }; class Solution { public: vector<int> maxSlidingWindow(vector<int>& nums, int k) { MyQueue que; vector<int> res; for (int i = 0; i < k; i++) { que.push(nums[i]); } res.push_back(que.getMaxValue()); for (int i = k; i < nums.size(); i++) { que.pop(nums[i - k]); que.push(nums[i]); res.push_back(que.getMaxValue()); } return res; } };

第二题

这一题用的是优先队列来解决前k个元素的问题,优先队列的底层是大顶堆和小顶堆来实现的,也就是我们在使用优先队列的时候是需要传入我们使用的是那个堆的cmp函数。这一题主要的难点就是我们到底是使用大顶堆还是小顶堆,大顶堆的根节点是最大值,我们每一次插入结点都会把堆顶的元素弹出去,然后把新加入的元素放入堆顶,然后从根节点开始重新调整这个堆,小顶堆也是一样。所以如果我们使用的是大顶堆,我们每一次弹出去的是最大频率的那一个,最后维护了k个最小的频率元素。所以我们应该使用小顶堆

class Solution { public: class cmp { public: bool operator()(const pair<int, int>& a, const pair<int, int>& b) { return a.second > b.second; } }; vector<int> topKFrequent(vector<int>& nums, int k) { unordered_map<int, int> umap; for(int i = 0; i < nums.size(); i++) { umap[nums[i]]++; } priority_queue<pair<int, int>, vector<pair<int, int>>, cmp> que; for(auto iter = umap.begin(); iter != umap.end(); iter++) { que.push(*iter); if (que.size() > k) { que.pop(); } } vector<int> res(k, 0); for (int i = k - 1; i >= 0; i--) { res[i] = que.top().first; que.pop(); } return res; } };

对于堆的调整代码,我这里也给出来了:这里传递的i就是1,因为是堆顶,然后不断的向下调整

void DownAdjust(int a[], int i, int n) { // 向下调整 int now = i; // 当前结点 int next; // 值最大的孩子 while(2 * now <= n) { // now的左子树存在,也就是说当前结点至少有一个孩子 next = 2 * now; if(2 * now + 1 <= n && a[2 * now + 1] > a[next]) { // 右子树存在,且右孩子值更大 next = 2 * now + 1; } if(a[now] < a[next]) { // 父亲比孩子小 std::swap(a[now], a[next]); now = next; // 再继续向下调整 }else { // 如果父亲比孩子大,说明已经是大顶堆了,不需要调整了 break; } } }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/26 10:39:00

水电站水轮机选型:从水文分析到全生命周期成本决策

1. 项目概述&#xff1a;为什么水轮机选型是水电站的“心脏手术”干了十几年水电工程&#xff0c;从深山老林里的小型径流式电站到江河干流上的大型枢纽&#xff0c;我最大的体会就是&#xff1a;一个水电站的成败&#xff0c;在图纸阶段就决定了七成。而决定这七成的关键&…

作者头像 李华
网站建设 2026/6/26 10:38:36

MCP16251/2同步升压转换器:低功耗IoT设备电源设计实战

1. 项目概述&#xff1a;为什么我们需要关注这颗“小芯片”&#xff1f;在嵌入式系统、便携式设备和电池供电产品的世界里&#xff0c;电源管理永远是那个最基础、最核心&#xff0c;却又常常被忽视的环节。你可能花了很多心思在MCU选型、传感器精度或者无线通信协议上&#xf…

作者头像 李华
网站建设 2026/6/26 10:37:53

从零构建802.15.4星型网络:MAC层实现与低功耗设计详解

1. 项目概述&#xff1a;从零构建一个802.15.4星型网络如果你正在开发一个低功耗的无线传感器网络&#xff0c;比如智能家居的传感器节点、工业数据采集器或者环境监测设备&#xff0c;那么你大概率绕不开IEEE 802.15.4这个标准。它就像是无线物联网世界的“方言”&#xff0c;…

作者头像 李华
网站建设 2026/6/26 10:35:01

蓝牙SCO音频时钟同步:环形缓冲区与插值技术详解

1. 蓝牙SCO音频传输的核心挑战与插值技术原理在嵌入式蓝牙音频开发领域&#xff0c;无论是做蓝牙耳机、车载免提还是对讲机&#xff0c;工程师们最头疼的问题之一&#xff0c;就是音频通话时偶尔出现的“咔哒”声、断续或者微妙的音调变化。这些问题往往不是信号强度不够&#…

作者头像 李华
网站建设 2026/6/26 10:33:07

深入解析NXP QMan硬件队列管理器:架构、原理与高性能驱动实践

1. 项目概述与核心价值 在嵌入式系统和网络处理器领域&#xff0c;尤其是面对海量数据包转发、实时信号处理这类对吞吐量和延迟有极致要求的场景&#xff0c;软件层面的队列管理往往会成为性能瓶颈。CPU周期被大量消耗在锁竞争、内存分配和上下文切换上&#xff0c;难以满足线速…

作者头像 李华