栈和队列part03

news/2024/11/13 14:55:40/文章来源:https://www.cnblogs.com/yuehuaicnblogs/p/18353916

今天学习了队列的常见题型:

  1. 滑动窗口最大值,先进先出不难想到队列,最大值可以考虑优先队列,但是此题还是典型的单调队列(需要自己实现)
  2. 前k个高频元素,维护最大值常用优先队列,注意选的最小堆

7. 239滑动窗口最大值(队列)

题目:给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回 滑动窗口中的最大值 。

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       31 [3  -1  -3] 5  3  6  7       31  3 [-1  -3  5] 3  6  7       51  3  -1 [-3  5  3] 6  7       51  3  -1  -3 [5  3  6] 7       61  3  -1  -3  5 [3  6  7]      7

每次动一下,前出一个,后进一个,可以想到队列。所以重点就是如何求窗口内的最大值。

a. 法一:优先队列

最大值,可以想到优先队列(堆),其中的大根堆可以实时维护一系列元素中的最大值。

对于本题而言,初始时,将数组 nums 的前 k 个元素放入优先队列中。每当我们向右移动窗口时,就可以把一个新的元素放入优先队列中,此时堆顶的元素就是堆中所有元素的最大值。然而这个最大值可能并不在滑动窗口中,在这种情况下,这个值在数组 nums中的位置出现在滑动窗口左边界的左侧。因此,当后续继续向右移动窗口时,这个值就永远不可能出现在滑动窗口中了,可以将其永久地从优先队列中移除。

不断地移除堆顶的元素,直到其确实出现在滑动窗口中。此时,堆顶元素就是滑动窗口中的最大值。为了方便判断堆顶元素与滑动窗口的位置关系,可以在优先队列中存储二元组 (num,index),表示元素num在数组中的下标为 index

vector<int> maxSlidingWindow(vector<int>& nums, int k) {int n = nums.size();// 先一口气放k个priority_queue<pair<int, int>> q;for (int i = 0; i < k; ++i) {q.emplace(nums[i], i);}vector<int> ans = {q.top().first};// 之后循环,每次放入一个,弹出最顶端的即可for (int i = k; i < n; ++i) {q.emplace(nums[i], i);while (q.top().second <= i - k) {q.pop();}ans.push_back(q.top().first);}return ans;
}

b.单调队列

这是单调队列的经典题目。

(1)需要一个什么样的队列

需要的队列是这样的:每次窗口移动的时候,调用que.pop(滑动窗口中移除元素的数值),que.push(滑动窗口添加元素的数值),然后que.front()就返回我们要的最大值。队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队列里的元素数值是由大到小的。那么这个维护元素单调递减的队列就叫做单调队列,即单调递减或单调递增的队列。

239.滑动窗口最大值

对于上图中窗口里的元素{2, 3, 5, 1 ,4},单调队列里只维护{5, 4} 就够了,保持单调队列里单调递减,此时队列出口元素就是窗口里最大元素。

(2)队列如何配合滑动?

这只是最开始的窗口,此单调队列里维护着{5, 4} 怎么配合窗口进行滑动呢?

设计单调队列的时候,pop,和push操作要保持如下规则:

  1. pop(value):如果窗口移除的元素value等于单调队列的出口元素,那么队列弹出元素,否则不用任何操作
  2. push(value):如果push的元素value大于入口元素的数值,那么就将队列入口的元素弹出,直到push元素的数值小于等于队列入口元素的数值为止

保持如上规则,每次窗口移动的时候,只要que.front()就可以返回当前窗口的最大值。

为了更直观的感受到单调队列的工作过程,以题目示例为例,输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3,动画如下:

239.滑动窗口最大值-2
(3)使用什么数据结构来实现这个队列

使用双端队列dequeue

  1. 构造函数

    • deque<T>:默认构造一个空的双端队列。
    • deque<T> d(n, value):构造一个包含 nvalue 值的双端队列。
  2. 元素访问

    • operator[]:随机访问,返回指定位置的元素,例如 d[i]
    • at(index):返回指定位置的元素并进行边界检查。
    • front():返回队列第一个元素的引用。
    • back():返回队列最后一个元素的引用。
  3. 修改操作

    • push_back(value):在双端队列的末尾添加一个元素。
    • push_front(value):在双端队列的开头添加一个元素。
    • pop_back():删除并返回双端队列末尾的元素。
    • pop_front():删除并返回双端队列开头的元素。
    • insert(position, value):在指定位置插入一个元素。
    • emplace_back(args...):在末尾直接构造一个元素,避免额外的拷贝或移动操作。
    • emplace_front(args...):在开头直接构造一个元素。
    • emplace(position, args...):在指定位置直接构造一个元素。高效的插入方式。
    • erase(position):删除指定位置的元素。
    • erase(begin, end):删除指定范围 [begin, end) 内的元素。
    • clear():清空整个双端队列。
    • resize(new_size):调整双端队列的大小。
  4. 容量操作

    • size():返回双端队列中元素的个数。
    • empty():判断双端队列是否为空。
  5. 迭代器操作

    • begin():返回指向第一个元素的迭代器。
    • end():返回指向最后一个元素之后的迭代器。
  6. 赋值操作

    • assign(n, value):将双端队列的内容替换为 nvalue 值。
    • assign(begin, end):用迭代器范围 [begin, end) 替换双端队列的内容。
class Solution {
private:class MyQueue { //单调队列(从大到小)public:deque<int> que; // 使用deque来实现单调队列// 每次弹出的时候,比较当前要弹出的数值是否等于队列出口元素的数值,如果相等则弹出。// 同时pop之前判断队列当前是否为空。void pop(int value) {if (!que.empty() && value == que.front()) {que.pop_front();}}// 如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,直到push的数值小于等于队列入口元素的数值为止。// 这样就保持了队列里的数值是单调从大到小的了。void push(int value) {while (!que.empty() && value > que.back()) {que.pop_back();}que.push_back(value);}// 查询当前队列里的最大值 直接返回队列前端也就是front就可以了。int front() {return que.front();}};
public:vector<int> maxSlidingWindow(vector<int>& nums, int k) {MyQueue que;vector<int> result;for (int i = 0; i < k; i++) { // 先将前k的元素放进队列que.push(nums[i]);}result.push_back(que.front()); // result 记录前k的元素的最大值for (int i = k; i < nums.size(); i++) {que.pop(nums[i - k]); // 滑动窗口移除最前面元素que.push(nums[i]); // 滑动窗口前加入最后面的元素result.push_back(que.front()); // 记录对应的最大值}return result;}
};
(4)时空复杂度
  • 时间复杂度: O(n)
  • 空间复杂度: O(k)

直观感受,在队列中 push元素的过程中,还有pop操作,不是纯粹的O(n)。其实nums 中的每个元素最多也就被 push_backpop_back 各一次,没有任何多余操作,所以整体的复杂度还是 O(n)。

8. 347前 K 个高频元素

题目:给定一个非空的整数数组,返回其中出现频率前 k 高的元素。

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

此题的解决思路有以下几步:

  1. 统计频率

    使用哈希表map来统计:

    unordered_map<int, int> map; // map<nums[i],对应出现的次数>
    for (int i = 0; i < nums.size(); i++) {map[nums[i]]++;
    }
    
  2. 按照频率排序

    解法一:使用快排把所有数字统计。但是需要将map转换为vector的结构,然后对整个数组进行排序

    // 比较函数,用于排序
    bool compare(const std::pair<int, int> &a, const std::pair<int, int> &b) {return a.second > b.second;  // 频率高的排在前面
    }    
    // 将频率和数字存储在一个 vector 中
    std::vector<std::pair<int, int>> freqVec(freqMap.begin(), freqMap.end());
    // 按照频率排序
    std::sort(freqVec.begin(), freqVec.end(), compare);
    

    解法二:这种场景下,其实只需要维护k个有序的序列就可以了,所以使用优先级队列是最优的。用小顶堆,因为要统计最大前k个元素,只有小顶堆每次将最小的元素弹出,最后小顶堆里积累的才是前k个最大元素。

  3. 取出频率最大的k个元素

class Solution {
public:// 小顶堆class mycomparison {public:bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {return lhs.second > rhs.second;}};vector<int> topKFrequent(vector<int>& nums, int k) {// 要统计元素出现频率unordered_map<int, int> map; // map<nums[i],对应出现的次数>for (int i = 0; i < nums.size(); i++) {map[nums[i]]++;}// 对频率排序// 定义一个小顶堆,大小为kpriority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;// 用固定大小为k的小顶堆,扫面所有频率的数值for (unordered_map<int, int>::iterator it = map.begin(); it != map.end(); it++) {pri_que.push(*it);if (pri_que.size() > k) { // 如果堆的大小大于了K,则队列弹出,保证堆的大小一直为kpri_que.pop();}}// 找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组vector<int> result(k);for (int i = k - 1; i >= 0; i--) {result[i] = pri_que.top().first;pri_que.pop();}return result;}
};
  1. 使用unorder_map的场景和语法

  2. 对于全体排序使用快排,维护一部分最大值和最小值常常考虑优先队列priority_queue

    priority_queue<pair<int,int>,vector<pair<int,int>>,mycomp>pri_pue

  3. 使用最大和最小堆中的哪一个,这个是典型题

  4. 小顶堆倒叙输出,其实只需要正常pop,但是倒着放到result里。

  • 时间复杂度: O(nlogk)
  • 空间复杂度: O(n)

今日古诗

鹧鸪天·送人
辛弃疾〔宋代〕

唱彻《阳关》泪未干,功名馀事且加餐。浮天水送无穷树,带雨云埋一半山。
今古恨,几千般,只应离合是悲欢?江头未是风波恶,别有人间行路难!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/781424.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【1.0版】【MYSQL安全】导入导出相关操作

主题 导入导出相关操作一、load_file()二、into outfile一、load_file() load_file(file_name):读取文件并返回该文件的内容作为一个字符串使用条件:A、 必须有权限读取并且文件必须完全可读 and (select count() from mysql.user)>0 #如果结果返回正常,说明具有读写权限。…

栈和队列part01

今天学习了栈和队列的第一部分。基础知识 用栈模拟队列(双栈) 用队列模拟栈(一个队列,但是需要重复将队头元素写到队尾) 栈的基本应用(括号匹配、删除重复项、逆波兰表达式)1. 基础知识栈和队列是以底层容器完成其所有的工作,对外提供统一的接口,底层容器是可插拔的(…

2-梯度下降算法

梯度下降算法只能保证找到的是局部最优,不是全局最优平常我们经过大量实验,发现局部最优点不是很多,所以可以使用梯度下降算法。 但是还要提防鞍点下面进行实现梯度下降算法点击查看代码 import numpy as np import matplotlib.pyplot as pltx_data = [1.0, 2.0, 3.0] y_dat…

5.Java方法

什么是方法Java方法是语句的集合,它们在一起执行一个功能。方法是解决一类问题的步骤的有序组合。 方法包含于类或对象中。 方法在程序中被创建,在其他地方被调用。设计方法的原则:方法的本意是功能块,就是实现某个功能的语句块的集合。我们设计方法的时候,最好保持方法的…

Maven的概述及基本使用示例详解

一、Maven简介Apache Maven是一个项目管理和构建工具,它基于项目对象模型(POM)的概念,通过一小段描述信息来管理项目的构建、报告和文档 官网:http://maven.apache.org/项目对象模型(Project Object Model) 依赖管理模型(Dependency) 插件(Plugin)1.1、Maven概述Mav…

l洛谷 P7870 兔已着陆——题解

洛谷P7870题解传送锚点摸鱼环节 「Wdoi-4」兔已着陆 题目背景 铃瑚和清兰是从月之都到达幻想乡的两只月兔。正因为降落到了幻想乡进行调查,因此她们通过开团子屋制作团子出售的方式,在幻想乡生活。 为了应对越发繁荣的市场,她们向河城荷取购置了一台团子机器,可以高效地生产…

038.Vue3入门,使用keep-alive让组件保持存活

1、App.vue代码如下:<template><button @click="change">切换组件</button><p></p><keep-alive><component :is="tabComponent"></component></keep-alive> </template><script> impo…

铂繁达:如何提升自身能力以更好开展互联网项目

互联网上有很多副业项目可供选择,但为何有些人无法从中获利呢?关键在于执行力,且不同的在线机遇往往需要不同的技能和知识储备。在挑选和选择这些机会时,我们应当首先确保自己拥有所需的技能或具备学习与提升这些技能的意愿。现下大家对副业的需求越来越大。确实,互联网上…

037.Vue3入门,动态组件

1、App.vue代码如下:<template><component :is="tabComponent"></component><button @click="change">切换组件</button> </template><script> import Child1 from "./view/Child1.vue" import Chil…

排列组合:公式及推导

组合数学入门排列组合:公式及推导 引入 定义: 排列:从指定个数的元素中取出指定个数的元素进行排序;(考虑元素的顺序) 组合:从给定个数的元素中仅仅取出指定个数的元素;(不考虑元素的顺序) 加法&乘法原理 加法原理: 完成一个工程可以有 \(n\) 类办法, \(a_i(i\in[1…