单调栈:(C++)

在题目的要求中,存在先进后出(即在前面的数据需要遍历到后面的某一数据时才能确定计算值)单调栈在一部分解题场景中避免了暴力解法的高时间复杂度问题,但是在做题过程中视情况而定,有些题目的最优解不一定使用单调栈,需要灵活解题,多思考一下或许有更加简便的算法和逻辑。

LeetCode 经典题目:

1. 「力扣」第84: 柱状图中最大的矩形

算法思想:

创建一个保存元素下标的 整形栈,遍历数组依次入栈:

  1. 当前值比栈顶元素大时,入栈当前元素下标
  2. 当前值比栈顶元素小时,说明栈顶元素此时可以判断面积了->出栈
  3. 需要注意的是出栈后的栈顶元素判断区间(宽)= 当前数组的的下标i与现在的栈顶元素的距离-1
  4. 出栈的栈顶元素对应的数组值为(高)
  5. 在首位添加哨兵(值为0的元素),在下标处理计算、首尾判断时无需特殊处理(类似链表头结点删除),更为简便

class Solution {
public:
int largestRectangleArea(vector<int>& heights) {stack<int>s;int len = heights.size() + 2;vector<int>h2;h2.push_back(0);//头哨兵for(int i = 0;i< heights.size(); i++){h2.push_back(heights[i]);//复制中间元素}h2.push_back(0);//尾哨兵//转换新的数组(首尾哨兵结点)int area = 0;s.push(0);//头哨兵入栈,便于计算间距for (int i = 1; i < h2.size(); i++){if (h2[i] >= h2[s.top()])s.push(i);else{while (h2[i] < h2[s.top()]){int tmp = s.top();s.pop();int a = (i - s.top() - 1) * h2[tmp];area = area < a ? a : area;}i--;//当前的i 作用于出栈计算之前不能确定的面积,//但当所有符合条件的下标出栈后,i下标对应的栈顶元素也重新对应,//应当再次对i是否入栈做判断,i--再给一次机会}}return area;
}
};

2. 「力扣」第42题:接雨水

算法思想:

  1. 用两侧的高墙才能将水围住,只需要找到比当前墙更高或一样高的另一面墙才能计算出之间的水量,(间距*墙高-中间的矮墙),得到这一区间的水量,下标从后一面墙继续向结尾遍历
  2. 需要注意的是上面的设想存在一种情况,当前面的墙高于后面的所有墙,则无法判断,所以在确认以第一面墙的标准的时候,需要向后寻找最高的墙是否存在高于当前的墙,如果没有,则将第一面墙的高度降低为后续最高墙一致的高度,确保算法无遗漏
  3. 具体实现:
    1. 创建栈保存元素下标,当后续元素小于栈顶元素时(中间低墙,直接跳过)
    2. 遍历的元素大于或等于栈顶元素时,此时可以确定中间的积水区域,当前(下标i - 栈顶元素下标)*栈顶元素的数值-区间中间的元素”低墙 “,不断累加
    3. 计算积水区域结束后, 后一面墙若不是最后一个元素,则入栈(并作最大值判断)

算法思想2:

数组从左向右计算该元素后可以累计的积水量,再从右向左重复判断,两次的结果取最小值累加为结果

class Solution {
public:
int trap(vector<int>& height) {stack<int>s;int sum = 0;s.push(0);if(height.size()>1){int max = *max_element(height.begin() + s.top() + 1, height.end());if (height[0] > max)height[0] = max;}for (int i = 1; i < height.size(); i++){if (height[i] >= height[s.top()]) {sum += (i - s.top() - 1) * height[s.top()];for (int j = s.top() + 1; j < i; j++)sum -= height[j];s.pop();if(i!=height.size()-1){s.push(i);int max0 = *max_element(height.begin() + s.top() + 1, height.end());if (height[i] > max0)height[i] = max0;}}}return sum;
}
};

3. 「力扣」第739题:每日温度

算法思想:

创建栈保留数组下标

  1. 入栈0号下标,用作第一次元素值判断
  2. 当遍历的元素值大于栈顶元素时,计算间距保存到数组并出栈
  3. 若遍历元素小于栈顶元素时,入栈,后序遍历过程中才能得到答案
  4. esay 的实现了
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {vector<int>v(temperatures.size());stack<int>s;s.push(0);int i;for (i = 1; i < temperatures.size(); i++){if (temperatures[i] > temperatures[s.top()]){while (s.size() > 0 && (temperatures[i] > temperatures[s.top()])){v[s.top()] = i - s.top();s.pop();}s.push(i);}elses.push(i);}return v;
}
};

4. 「力扣」第496题:下一个更大元素

算法思想:

  1. 将nums2的元素从后向前遍历,顺序入栈,且始终保持栈内元素递减,则栈内元素的下一元素为所求的nums2 的下一更大元素
  2. 当栈内为空,直接入栈;
  3. 栈不为空,当前元素大于栈顶元素,出栈,保持栈内顺序递减
  4. 在保持当前新元素入栈后栈内元素依旧递减的状态(此时新元素未入栈)
    1. 当栈为空,说明nums2不存在下一更大元素
    2. 当栈不为空,则栈顶为所求的下一更大元素
  1. 这些所求信息用哈希表存储,key为所求元素,value为下一更大元素
  2. 在所有信息都保存在哈希表的基础上,只需查询map[key]对应的值返回结果即可
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {//单调栈vector<int>v(nums1.size(), -1);unordered_map<int, int>hashmap;stack<int>s;for (int i = nums2.size() - 1; i >= 0; i--){if (s.empty()!=1&&nums2[i] > s.top()){while (s.empty() != 1 && nums2[i] > s.top()){s.pop();}}hashmap[nums2[i]] = s.empty() ? -1 : s.top();s.push(nums2[i]);}for (int i = 0; i < nums1.size(); i++){v[i] = hashmap[nums1[i]];}return v;}
};

5. 「力扣」第316题:去除重复字母

class Solution {
public:
string removeDuplicateLetters(string s) {unordered_map<char, int>map;unordered_map<char, bool>b;for (int i = 0; i < s.size(); i++){map[s[i]]++;b[s[i]] = 0;}string s2 = "";for (auto it=s.begin();it!=s.end();it++  ){if (b[*it] == 0)//如果栈内不存在{while (!s2.empty() && s2.back() > *it&& map[s2.back()] >0)//如果栈头大于 it{b[s2.back()] = 0;s2.pop_back();}s2.push_back(*it);b[*it] = 1;}map[*it]--;//所有it在循环遍历时依次--;与是否入栈无关}return s2;
}
};

谁说用单调栈解题就一定要用栈stack当容器了? string不行吗?

算法思想:

这道题解决两个问题:删除重复项、字典序排列!

  1. 用哈希表unordered_map1<char,int>实现,键值对键存数据,value存次数
  2. 第二个问题需要结合第一个问题的哈希表实现排列:

哈希表2unordered_map1<char,bool> value 值为0,代表是否当前元素已在栈中入栈value为1,出栈后改为0;

  1. 字典序排列:
    1. 当遍历的当前元素不是重复元素时则直接入栈(map1[key]==1时)
    2. 当遍历的当前元素小于栈顶元素的字典序时,并且栈顶元素为重复元素时:出栈入新元素(!s2.empty() && s2.back() > *it&& map[s2.back()] >0)
    3. 当遍历到栈内存在的元素时(map2[]==1;),跳过,因为栈内已经是最优解
    4. 在每次遍历元素结束后,将该元素的map1计数器--

6. 「力扣」第402题:移掉K位数字

算法思想:

  1. 从数组开头向后遍历的过程中入栈,必须保持入栈的数据保持递增
  2. 若后面入栈的数据小于栈内栈顶元素,则出栈直到入栈新数据保持所有数据有序
  3. 每次出栈则代表删除一个元素,移除的计数器加加
  4. 若计数器在未遍历完所有数组元素时已经归零,则直接将剩余的数组元素入栈
  5. 若遍历完所有数组元素后,移除的计数器依然未归零,(因为当前栈内数据保持有序,则栈顶元素为最大值),则移除栈顶元素直至计数器归零
  6. 此时栈底到栈顶为有序递增,出栈后数据顺序必然颠倒,所以在返回之前将数组元素reverse
  7. 特殊判断:
    1. 若栈内为空,则返回string “0”
    2. 若数据头部有0 eg: ”001“ 当删除头部 0,再输出
class Solution {
public:
string removeKdigits(string num, int k) {stack<int>s;string s2;for (int i = 0; i < num.size(); i++){while(!s.empty() && num[i] <s.top()&&k>0){s.pop();k--;}s.push(num[i]);}//未删完从尾巴删while (k > 0){s.pop();k--;}//判空返回0if (s.empty()){s2.push_back('0');return s2;}while (!s.empty()){s2.push_back(s.top());s.pop();}//栈底含有0while (s2.back() == '0'&&s2.size()>1)s2.pop_back();reverse(s2.begin(), s2.end());return s2;
}
};

7. 「力扣」第581题:最短无序连续子数组

这道题的解题并没有用到栈,但是需要记忆该题的算法思想:

谁说一定要用单调栈解题?

  1. 寻找中间的子串使得整段数据有序
  2. 将原数据复制一份并排序,对比左右两侧最大的相等区间,中间部分即为所求
  3. 这道题虽然也出现在单调栈的题目集合中,但是使用单调栈的方法不一定简单易实现,需要我们灵活判断使用
class Solution {
public:
int findUnsortedSubarray(vector<int>& nums) {
//排序后,比对原数组,得到左边界与右边界vector<int>v = nums;sort(v.begin(), v.end());int i, j;for (i = 0, j = nums.size() - 1; i < j; ){if (nums[i] == v[i])i++;if (nums[j] == v[j])j--;else if (nums[i] != v[i] && nums[j] != v[j])break;}if (i>=j)return 0;return j-i+1;
}
};

总结:

  1. 不能判断的元素下标入栈,直至可以判断时出栈;
  2. 注意利用元素的下标区间的作用
  3. 是否需要首位哨兵结点,避免多余的判断

题目也许有简单算法、实现方法,也许我们用了另一种很艰难的算法实现了,但是也并不能说明这道题我们解的很好,要尝试多练习、多思考,不断寻找最优解

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

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

相关文章

百度智能云数据仓库 Palo 实战课程

通过本课程&#xff0c;您将学习如何使用 Palo 构建高性能、低延迟的分布式数仓服务&#xff0c;掌握数据建模、数据导入、查询优化和系统调优等技能&#xff0c;掌握如何管理和运维 Palo 集群&#xff0c;提高数据处理和分析的效率。同时&#xff0c;我们将进一步向您介绍 Pal…

2024OD机试卷-螺旋数字矩阵 (java\python\c++)

题目:螺旋数字矩阵 题目描述 疫情期间,小明隔离在家,百无聊赖,在纸上写数字玩。他发明了一种写法: 给出数字个数 n (0 < n ≤ 999)和行数 m(0 < m ≤ 999),从左上角的 1 开始,按照顺时针螺旋向内写方式,依次写出2,3,…,n,最终形成一个 m 行矩阵。 小明对这…

2024下载旧版本谷歌浏览器和谷歌驱动器chromedriver,亲测有效

2024下载旧版本谷歌浏览器和谷歌驱动器chromedriver,亲测有效 1. 下载旧版本谷歌浏览器 找了很多博客&#xff0c;实验了很多种&#xff0c;我发现最有效的是下面的网址&#xff0c;可能需要一些科技。 但是下载下来的谷歌浏览器版本是ok的。拿来就能用&#xff0c;亲测有效…

QT创造一个新的类(柱状图的类),并关联属性和方法

1.以在UI上添加柱状图的类为例&#xff08;Histogram&#xff09; #ifndef STUDY_HISTOGRAM_H #define STUDY_HISTOGRAM_H#include <QVector> #include <QWidget>// 前向声明 QT_BEGIN_NAMESPACE class QColor; class QRect; class QString; class QPaintDevice; …

【机器学习300问】84、AdaGrad算法是为了解决什么问题?

神经网络的学习的目的是找到使损失函数的值尽可能小的参数。这是寻找最优参数的问题&#xff0c;解决这个问题的过程称为最优化。因为参数空间非常复杂&#xff0c;无法轻易找到最优解&#xff0c;而且在深度神经网络中&#xff0c;参数的数量非常庞大&#xff0c;导致最优化问…

【SpringBoot篇】基于Redis分布式锁的 误删问题 和 原子性问题

文章目录 &#x1f354;Redis的分布式锁&#x1f6f8;误删问题&#x1f388;解决方法&#x1f50e;代码实现 &#x1f6f8;原子性问题&#x1f339;Lua脚本 ⭐利用Java代码调用Lua脚本改造分布式锁&#x1f50e;代码实现 &#x1f354;Redis的分布式锁 Redis的分布式锁是通过利…

基于FPGA的音视频监视器,音视频接口采集器的应用

① 支持1路HDMI1路SDI 输入 ② 支持1路HDMI输出 ③ 支持1080P高清屏显示实时画面以 及叠加的分析结果 ④ 支持同时查看波形图&#xff08;亮度/RGB&#xff09;、 直方图、矢量图 ⑤ 支持峰值对焦、斑马纹、伪彩色、 单色、安全框遮幅标记 ⑥ 支持任意缩放画面&#xff0c;支…

远程桌面连接不上怎么连服务器,原因是什么?如何解决?

远程桌面连接不上怎么连服务器&#xff0c;原因是什么&#xff1f;如何解决&#xff1f; 面对远程桌面连接不上的困境&#xff0c;我们有办法&#xff01; 当你尝试通过远程桌面连接服务器&#xff0c;但遭遇连接失败的挫折时&#xff0c;不要慌张。这种情况可能由多种原因引起…

Redis20种使用场景

Redis20种使用场景 1缓存2抽奖3Set实现点赞/收藏功能4排行榜5PV统计&#xff08;incr自增计数&#xff09;6UV统计&#xff08;HeyperLogLog&#xff09;7去重&#xff08;BloomFiler&#xff09;8用户签到&#xff08;BitMap&#xff09;9GEO搜附近10简单限流11全局ID12简单分…

【Docker】Docker部署Java程序

Maven中使用打包插件 <build><finalName>duanjian</finalName><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><mainClass…

性能测试 --概念

什么是性能测试 性能测试和功能测试都是在系统测试阶段运行, 两者有什么区别呢? 案例:豌豆射手和三线射手都是射手, 它们的功能都是向前发射豌豆进行攻击, 能够攻击到地面的僵尸. 但是从性能上来讲, 豌豆射手只能攻击到一路的僵尸, 而三线射手能同时攻击三路(注:放在边路实际…

【unity】用代码实现“碰到障碍后 运动对象的运动方向如何改变(反弹/滑行)”

想要实现的效果&#xff1a; 例1&#xff1a;飞出的弹丸&#xff0c;碰到墙壁后&#xff0c;反弹。【↘️| 】——>【↙️| 】 例2&#xff1a;向右下方【↘️】移动的对象&#xff0c;碰到右侧的墙壁 【↘️| 】 后&#xff0c;继续沿着着墙壁向下方移动【↓ | 】 为什么要…

npm run build 时出现语法报错 Module parse failed: Unexpected token

错误原因 出现这个错误一般是你使用了或者引用的包里出现了ES6的语法&#xff0c;但是你的webpack可能是4.x或者更低版本的&#xff0c; 这时候单靠webpack本身无法识别ES6的语法&#xff0c;但是升级webpack到5可能会与现有的代码产生兼容性问题&#xff0c;因此就需要引入b…

服务器内存占用不足会怎么样,解决方案

在当今数据驱动的时代&#xff0c;服务器对于我们的工作和生活起着举足轻重的作用。而在众多影响服务器性能的关键因素当中&#xff0c;内存扮演着极其重要的角色。 服务器内存&#xff0c;也称RAM&#xff08;Random Access Memory&#xff09;&#xff0c;是服务器核心硬件部…

2024-AIDD-人工智能药物设计-使用的软件包和网络服务器

Python 软件包 化学信息学和结构生物信息学&#xff1a; rdkit: http://rdkit.org/openbabel: https://openbabel.org/mdanalysis: https://www.mdanalysis.org/biopython: https://biopython.org/biopandas: https://biopandas.github.io/biopandas/opencadd: https://openca…

算法-靠谱的车(数位模拟)

import java.util.*; public class Main{public static void main(String[] args){Scanner innew Scanner(System.in);int nin.nextInt(),copyn;// 取每一位上的数放入数组List<Integer> listnew ArrayList<>();while(n!0){int resn%10;list.add(0,res);n/10;}// 转…

回炉重造java----双列集合(HashMap,TreeMap)

体系结构 ①基本操作: ②遍历方式: 第一种: 键找值&#xff0c;通过map.keySet()获取Map的键集合&#xff0c;通过键去匹配Map中的值 Set<String> strings map.keySet();for (String string : strings) {System.out.println(map.get(string));} 第二种: 键值对&…

【linux】vmtouch文件缓存管理工具

目录 vmtouch简介 用法 例子 统计文件或者目录在缓存中的记录 缓存文件到内存 其他类似工具 vmtouch简介 vmtouch是用c语言编写的文件缓存管理工具&#xff0c;适用用于所有类Unix系统。 作用&#xff1a; 1&#xff0c;查看文件系统缓存情况 2&#xff0c;将文件或目…

多个.C文件被编译为一个可执行文件的详细过程

多个.C文件被编译为一个可执行文件的详细过程 文章目录 多个.C文件被编译为一个可执行文件的详细过程前言一、一个.C文件的编译过程二、多个.C文件的链接过程1.文件信息2.链接过程3.makefile 总结 前言 C语言经典的 “hello world ” 程序从编写、编译到运行&#xff0c;看到屏…

【Qt 学习笔记】Qt常用控件 | 多元素控件 | List Widget的说明及介绍

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt常用控件 | 多元素控件 | List Widget的说明及介绍 文章编号&#x…