单调栈:(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);
		}
		else
			s.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--;
	}
	//判空返回0
	if (s.empty())
	{
		s2.push_back('0');
		return s2;
	}


	while (!s.empty())
	{
		s2.push_back(s.top());
		s.pop();
	}
	//栈底含有0
	while (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. 是否需要首位哨兵结点,避免多余的判断

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【原创】springboot+mysql物资库存管理系统设计与实现

个人主页&#xff1a;程序猿小小杨 个人简介&#xff1a;从事开发多年&#xff0c;Java、Php、Python、前端开发均有涉猎 博客内容&#xff1a;Java项目实战、项目演示、技术分享 文末有作者名片&#xff0c;希望和大家一起共同进步&#xff0c;你只管努力&#xff0c;剩下的交…

利用AI提高内容生产效率的五个方案

目录 如何利用AI提高内容生产效率? ​编辑方向一&#xff1a;自动化内容生成 方向二&#xff1a;内容分发与推广 方向三&#xff1a;内容分析与优化 方向四&#xff1a;图像和音频处理 方向五&#xff1a;自动编辑和校对 如何利用AI提高内容生产效率? 简介&#xff1a…

车载电子电器架构 —— 应用软件开发(上)

车载电子电器架构 —— 应用软件开发(上) 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明…

CellChat包文献介绍

Inference and analysis of cell-cell communication using CellChat - PubMed (nih.gov) 目录 在线数据 摘要 基础介绍 分析结果 1&#xff0c;概述 2&#xff0c;识别预测通路 3&#xff0c;连续的信号转导 4&#xff0c;预测空间共定位细胞群之间的关键信号转导事件…

企业活动想联系媒体报道宣传如何联系媒体?

在企业的宣传推广工作中,我曾经历过一段费事费力、效率极低的时期。那时,每当公司有重要活动或新项目需要媒体报道时,我便要一家家地联系媒体,发送邮件、打电话,甚至亲自登门拜访,只为求得一篇报道。然而,这样的过程充满了不确定性和挑战,时常让我感到焦虑和压力山大。 记得有一…

vue3专栏项目 -- 项目介绍以及准备工作

这是vue3TS的项目&#xff0c;是一个类似知乎的网站&#xff0c;可以展示专栏和文章的详情&#xff0c;可以登录、注册用户&#xff0c;可以创建、删除、修改文章&#xff0c;可以上传图片等等。 这个项目全部采用Composition API 编写&#xff0c;并且使用了TypeScript&#…

亚马逊产品排名提升全攻略:自养号测评干货

之前我们一同探讨了亚马逊产品排名的多种类型&#xff0c;现在让我们回到正题&#xff0c;探讨一下如何才能有效地提升产品排名&#xff0c;从而吸引并抓住平台的流量&#xff0c;最终将其转化为可观的销量。 首先&#xff0c;卖家必须明晰亚马逊的排名机制&#xff0c;它主要基…

网页版Figma汉化

最近学习Figma&#xff0c;简单介绍一下网页版Figma的汉化方法 1.打开网址&#xff1a;Figma软件汉化-Figma中文版下载-Figma中文社区 2.下载汉化插件离线包 解压汉化包 3.点开谷歌的管理扩展程序 4.点击加载已解压的扩展程序&#xff0c;选择刚刚解压的包 这样就安装好了汉化…

从0到1开发一个vue3+ts项目(一)

1. 环境配置 1.1 安装node 使用官方安装程序 前往 Node.js 官网&#xff1a;访问 Node.js 官网&#xff0c;下载适合你操作系统的安装程序。运行安装程序&#xff1a;下载完成后&#xff0c;双击安装程序并按照提示进行安装。验证安装&#xff1a;安装完成后&#xff0c;在终…

顺序表经典算法OJ题-- 力扣27,88

题1&#xff1a; 移除元素 题2&#xff1a; 合并两个有序数组 一&#xff1a;题目链接&#xff1a;. - 力扣&#xff08;LetCode&#xff09; 思路&#xff1a;&#xff08;双指针法&#xff09; 创建两个变量src&#xff0c;dst 1&#xff09;若src指向的值为val&#xf…

Qt复习第二天

1、菜单栏工具栏状态栏 #include "mainwindow.h" #include "ui_mainwindow.h" #pragma execution_character_set("utf-8"); MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow) {ui->setupUi(this);//菜…

粤嵌—2024/4/26—跳跃游戏 ||

代码实现&#xff1a; 方法一&#xff1a;回溯 历史答案剪枝优化——超时 int *dis;void dfs(int k, int startindex, int *nums, int numsSize) {if (dis[startindex] < k) {return;}dis[startindex] k;for (int i 0; i < nums[startindex]; i) {if (startindex i &…

嫁接打印的技术要点

所谓嫁接打印&#xff0c;是一种增减材混合制造的方式。它将已成形的模具零件当作基座&#xff0c;在此基础上“生长”出打印的零件。其中基座通常采用传统加工方式制造&#xff0c;而打印部分则使用专用的金属粉末&#xff0c;通过 3D 打印技术成型。 嫁接打印之所以备受欢迎&…

4.nginx.pid打开失败以及失效的解决方案

一. nginx.pid打开失败以及失效的解决方案 1.错误图片&#xff1a; 2.解决方法 步骤1&#xff1a;进入这个目录 /var/run/nginx,提示没有文件或目录&#xff0c;则使用mkdir创建这个目录。 步骤2&#xff1a;然后 ./nginx -s reload 运行,是一个无效的PID 步骤3&#xff1a;使…

SMI接口

目录 SMI 接口帧格式读时序写时序 IP 设计IP 例化界面IP 接口IP 验证 SMI 接口 SMI&#xff08;Serial Management Interface&#xff09;串行管理接口&#xff0c;也被称作 MII 管理接口&#xff08;MII Management Interface&#xff09;&#xff0c;包括 MDC 和 MDIO 两条信…

【字符串】Leetcode 二进制求和

题目讲解 67. 二进制求和 算法讲解 为了方便计算&#xff0c;我们将两个字符串的长度弄成一样的&#xff0c;在短的字符串前面添加字符0&#xff1b;我们从后往前计算&#xff0c;当遇到当前计算出来的字符是> 2’的&#xff0c;那么就需要往前面进位和求余 注意&#xf…

《QT实用小工具·六十二》基于QT实现贝塞尔曲线画炫酷的波浪动画

1、概述 源码放在文章末尾 该项目实现了通过贝塞尔曲线画波浪动画&#xff0c;可控制 颜色密度速度加速度 安装与运行环境 语言&#xff1a;C 框架&#xff1a;Qt 11.3 平台&#xff1a;Windows 将屏幕水平平均分为10块&#xff0c;在一定范围内随机高度的12个点&#xff08;…

OAuth 2.0 和 OAuth 2.1

OAuth 2.0 和 OAuth 2.1比较&#xff1a; OAuth 2.0 和 OAuth 2.1 是授权框架的不同版本&#xff0c;它们用于允许应用程序安全地访问用户在另一个服务上的数据。以下是它们之间的一些主要区别&#xff1a; 安全性增强&#xff1a;OAuth 2.1 旨在提高安全性&#xff0c;它整合…

C语言/数据结构——每日一题(移除链表元素)

一.前言 今天在leetcode刷到了一道关于单链表的题。想着和大家分享一下。废话不多说&#xff0c;让我们开始今天的知识分享吧。 二.正文 1.1题目要求 1.2思路剖析 我们可以创建一个新的单链表&#xff0c;然后通过对原单链表的遍历&#xff0c;将数据不等于val的节点移到新…

MySQL索引(聚簇索引、非聚簇索引)

了解MySQL索引详细&#xff0c;本文只做整理归纳&#xff1a;https://blog.csdn.net/wangfeijiu/article/details/113409719 概念 索引是对数据库表中一列或多列的值进行排序的一种结构&#xff0c;使用索引可快速访问数据库表中的特定信息。 索引分类 主键索引&#xff1a…