单调栈:(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.rhkb.cn/news/324120.html

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

相关文章

AI图书推荐:ChatGPT全面指南—用AI帮你更健康、更富有、更智慧

你是否在努力改善你的健康&#xff1f; 你是否长期遭受财务困难&#xff1f; 你想丰富你的思想、身体和灵魂吗&#xff1f; 如果是这样&#xff0c;那么这本书就是为你准备的。 《ChatGPT全面指南—用AI帮你更健康、更富有、更智慧》&#xff08;CHATGPT Chronicles AQuick…

Java入门基础学习笔记4——开发Helloworld入门程序

Java程序开发的三个步骤&#xff1a; 1&#xff09;编写代码 2&#xff09;编译代码 3&#xff09;运行代码 注意事项&#xff1a; 第一个java程序建议使用记事本来编写。 建议代码文件名全英文、首字母大写、满足驼峰模式&#xff0c;源代码文件的后缀必须是.java 注意&a…

netty配置SSL、netty配置https(开发)

netty配置SSL、netty配置https&#xff08;开发&#xff09; 我们在开发下使用ssl&#xff0c;所用的证书将不被客户端信任。 转自&#xff1a;https://lingkang.top/archives/netty-pei-zhi-ssl 方案一 快速。使用netty提供的临时签发证书 private static SslContext sslC…

小程序(三)

十三、自定义组件 &#xff08;二&#xff09;数据方法声明位置 在js文件中 A、数据声明位置&#xff1a;data中 B、方法声明位置methods中&#xff0c;这点和普通页面不同&#xff01; Component({/*** 组件的属性列表*/properties: {},/*** 组件的初始数据*/data: {isCh…

TCP 连接,一端断电和进程崩溃有什么区别?

TCP 连接&#xff0c;一端断电和进程崩溃有什么区别&#xff1f; 前言主机崩溃进程崩溃有数据传输的场景客户端主机宕机&#xff0c;又迅速重启客户端主机宕机&#xff0c;一直没有重启 总结 前言 有的小伙伴在面试腾讯的时候&#xff0c;遇到了这么个问题&#xff1a; 这个属…

cocos中的meta文件有什么用?如何生成?

cocos中的.meta文件有什么用&#xff1f;如何生成&#xff1f; 1. .meta文件有什么用&#xff1f; Cocos Creator 会为 assets 目录下的每一个文件和目录生成一个同名的 meta 文件 示例 {"ver": "4.0.23", // 版本"importer": "typescr…

基于ESP32和ESP8266的物联网开发过程(二)

在做这个项目前&#xff0c;也做了一些调研。项目的初衷是想要用于智能家居。我比较了小米IoT、阿里云、ESPHOME、巴沙云、点灯科技和ONENET等几个平台。最终选择了Onenet&#xff0c;部分原因是之前用过它的多协议版本&#xff0c;但现在这个版本已经下线了。 小米IoT的公测名…

Linux - make与makefile

文章目录 什么是make和makefile如何使用依赖关系 和 依赖方法伪目标 写个程序-进度条换行和回车的区别 什么是make和makefile make是一个命令 makefile是一个文件 这就是make和makefile的本质 make和 ll , pwd ,su 一样都是命令 makefile和 test &#xff0c; test.c 一样都是…

GPT+Python近红外光谱数据分析

原文链接&#xff1a;GPTPython近红外光谱数据分析https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247603913&idx1&sn6eb8fd6f1abcdd8160815997a13eb03d&chksmfa82172ecdf59e389a860547a238bb86c7f38ae3baa14e97c7490a52ef2a2c206f88d503a5eb&token…

【WPF学习笔记(一)】WPF应用程序的组成及Window类介绍

WPF应用程序的组成及Window类介绍 WPF应用程序的组成及Window类介绍前言正文1、WPF介绍1.1 什么是WPF1.2 WPF的特点1.3 WPF的控件分类 2、XAML介绍2.1 XAML的定义2.2 XAML的特点2.3 XAML的命名空间 3、WPF应用程序组成3.1 App.config3.2 App.xaml3.3 App.xaml.cs3.4 MainWindow…

分布式存储CephFS最佳实践

文章来源于知乎文章&#xff1a; 分布式存储CephFS最佳实践 背景 近日&#xff0c;一朋友说他们企业内部想落地CephFS&#xff0c;让我帮忙写一份能落地的CephFS最佳实践。然后就顺便把文章整理到了这里。因能力水平以及认知有限&#xff0c;如有错漏烦请指正。 简介 CephF…

IP 地理定位神话与事实

ip地理定位是一项技术&#xff0c;用于通过访问设备的ip地址来获取地理位置信息&#xff0c;例如国家、城市、经纬度等。该技术广泛应用于网站内容自定义、广告定位、网络安全和用户分析等领域。它通过与包含ip地址和地理位置映射的大型数据库进行查询来工作&#xff0c;但在准…

聊聊测试团队管理

管理测试团队是一个复杂但至关重要的任务&#xff0c;它不仅关乎于保证软件产品的质量&#xff0c;还涉及到团队建设、流程优化、技能提升等多个方面。以下是一些关键策略&#xff0c;可以帮助您有效地管理测试团队&#xff0c;比如“持续培训和技术支持&#xff0c;明确目标&a…

99、技巧-下一个排列

这个问题要求生成一个数组的下一个排列。所谓“下一个排列”指的是&#xff0c;在所有数字相同但顺序不同的排列中&#xff0c;找出数字序列中刚好比当前序列大的下一个序列。如果当前序列已经是这些排列中的最大值&#xff0c;则下一个排列应该是最小的排列。 思路解释 要解…

QML 本地存储(Setting,sqlite)

Qt hello - 专注于Qt的技术分享平台 QML 原生的储存方有两种&#xff1a; 1&#xff0c;Settings 跟QWidget 中的QSettings 一样&#xff0c;可以简单的存储一些配置。 2&#xff0c;Sqlite sqlite数据库。可以存储一些复杂的数据。 一&#xff0c;Settings 我们以一个按钮的位…

【机器学习300问】81、什么是动量梯度下降算法?

动量梯度下降算法&#xff08;Momentum&#xff09;是利用指数加权移动平均的思想来实现梯度下降的算法。让我们先来回顾一下基础的梯度下降方法以及看看它有哪些不足之处。接着引出动量梯度下降算法&#xff0c;在理解了它的原理后看看它是如何规避之前方法的不足的。 如果不知…

Spring如何控制Bean的加载顺序

前言 正常情况下&#xff0c;Spring 容器加载 Bean 的顺序是不确定的&#xff0c;那么我们如果需要按顺序加载 Bean 时应如何操作&#xff1f;本文将详细讲述我们如何才能控制 Bean 的加载顺序。 场景 我创建了 4 个 Class 文件&#xff0c;分别命名为 FirstInitialization Se…

如何使用 ERNIE 千帆大模型基于 Flask 搭建智能英语能力评测对话网页机器人(详细教程)

ERNIE 千帆大模型 ERNIE-3.5是一款基于深度学习技术构建的高效语言模型&#xff0c;其强大的综合能力使其在中文应用方面表现出色。相较于其他模型&#xff0c;如微软的ChatGPT&#xff0c;ERNIE-3.5不仅综合能力更强&#xff0c;而且在训练与推理效率上也更高。这使得ERNIE-3…

第三节课,功能2:开发后端用户的管理接口-- postman--debug测试

一、如何使用postman 网址&#xff1a; https://www.postman.com/downloads/ 【Postman小白教程】五分钟学会如何使用Postman~_哔哩哔哩_bilibili postman安装使用_bowser agent在postman哪里-CSDN博客 二、下载后 登录&#xff0c;开始测试 2.1 关于postman 报错&#…

第十五届蓝桥杯python B组省赛

前言&#xff1a; 这是我第一次参加蓝桥杯&#xff0c;成绩并不理想&#xff0c;我反思了一下午&#xff0c;我的问题主要是知识点学不透&#xff0c;题目做的太少&#xff0c;而且学习的时候少数时间不专心&#xff0c;但是&#xff0c;我能感觉到我的学习能力并不弱&#xf…