Java-数据结构-栈与队列(常考面试题与单调栈)

在上一篇的学习中,我们学习了栈和队列的基本知识,以及它们对应都有哪些方法,在什么应用场景下如何使用,并且还对它们进行了模拟实现,而其实对于栈和队列的相关知识还远不止于此,而今天我们就对栈与队列进行复盘,认识更多使用它们的场景,夯实代码功底吧~

一、常考面试题-思维

以下习题在leetcode都是比较热门的题,并且大部分都能够代表一系列的解题方式,推荐大家自己多尝试几遍,练熟练透哦~

第一题、最小栈

📚 思路提示

该题要求我们实现一个拥有"能够直接获取栈内最小元素方法"的栈,并且要求时间复杂度为O(1)要知道我们之前所学习的栈可是没有这种高能的方法的~而这是为什么呢?

因为当我们把元素压入栈中之后想要再对其中的元素进行访问是做不到的,如果要强行访问,那么就需要将栈顶元素一个个拿出来,而当找到最小值的那一刻,该栈的元素也就都被掏空了,也就更不用说时间复杂度还达到了O(n)。

当然,做不到是因为只有一个栈,而我们可以采取创建辅助栈的方式,来模拟实现这种"O(1)查找最小元素"的栈具体的实现步骤如下所述

📕 创建一个辅助栈,用于存储当前"最小栈"中的最小值

📕 只有"入栈元素 <= 最小值"时,才入最小栈( == 时必须入栈,否则可能发生"普通栈有两个最小值,而最小栈只有一个最小值,最小值出栈后,最小栈中就不是当前最小值了"的情况)

📕 需要注意,"最小栈"中的最小值可能被出栈,所以辅助栈存储的最小值也要跟随一起改变

📕 注意出栈操作时,保证两个栈都不为空

图解

📖 代码示例

class MinStack {Stack<Integer> stack1;Stack<Integer> stack2;int n = Integer.MAX_VALUE;public MinStack() {stack1 = new Stack<>();stack2 = new Stack<>();}public void push(int node) {if (node < n) {n = node;}stack2.push(n);stack1.push(node);}public void pop() {stack1.pop();stack2.pop();if(!stack2.isEmpty() && stack2.peek() >= n){n = stack2.peek();}if(stack2.isEmpty()){n = Integer.MAX_VALUE;}}public int top() {return stack1.peek();}public int getMin() {return stack2.peek();}
}

第二题、字符串解码

📚 思路提示

该题要求我们将"数学表达式的字符串"改写成"展开后的字符串",光看测试用例大家或许觉得简单,但看似简单,其实需要注意的细节还是很多的,比如它还有三个测试用例:

这样看起来就有些让人抓心挠肝,难受的不行了。

而对于这题的解决方式,我们仍然是采用"辅助栈"的方法而且这次我们需要不止一个,而是需要"两个辅助栈"。

至于需要用两个辅助栈,是因为我们不能光存"倍数"或者"字符",因为我们想达到O(n)的时间复杂度,就要求我们遍历一次成功,但我们可以注意到:

以示例2为参考例子"3 [ a 2 [ c ] ]"    ==》 "accaccacc"

想要结果有"cc",就需要将 [ c ] 于它之前的 2 相对应,而我们遍历到 [ c ] 的时候,已经越过了 2 ,所以如果我们想不存储数字,是做不到这一点的。

而如果想有"acc",我们就必须再将 a 接在 cc 的前边,但是当我们合成 cc 后,早就遍历过了 a ,所以我们也必须用一个栈存储字符,才能得到最终的字符串。

而具体怎么存呢?我们可以通过一个"res"来存储临时的字符串,"multi"来存储临时的数字,' [ ' 来作为分段的符号,

📕 每当遇到 ' [ ' ,我们便将此时这一组字符串于数字存入栈中,以便不与其他部分搞混

📕 每当遇到 ' ] ' ,我们便将此时的"临时字符串"与"栈内数字"进行结合

(因为 ' [ ' 前数字是与 ' [ ' 后字符串结合后的因子,因此二者匹配)

图解

📖 代码示例

class Solution {public static String decodeString(String s) {int multi = 0;StringBuilder res = new StringBuilder();Stack<String> stack1 = new Stack<>();Stack<Integer> stack2 = new Stack<>();char[] str = s.toCharArray();for(int i = 0;i < str.length;i++){char c = str[i];if(c == '['){stack1.push(res.toString());stack2.push(multi);multi = 0;res = new StringBuilder();}else if(c == ']'){StringBuilder ss = new StringBuilder();int n = stack2.pop();for(int j = 0;j < n;j++){ss.append(res);}res = new StringBuilder(stack1.pop() + ss);}else if(Character.isDigit(c)){multi = multi * 10 + (c - 48);}else {res.append(c);}}return res.toString();}
}

第三题、逆波兰表达式求值

📚 思路提示

此题并不难,只要搞清了逆波兰表达式如何求,并且逆波兰表达式如何再还原成原式就好了~

所以我们直接看图,只要了解了过程,这题也就迎刃而解了~

图解

算数计算式转逆波兰表达式

波兰表达式求表达式的值:

(用后缀表达式求结果)

📕 遇到数字 放入栈内 遇到运算符 弹出栈顶

📕 两个元素 第一个元素是右操作数 第二个元素是左操作数

📕 然后再将结果放回栈内

📖 代码示例

class Solution {public int evalRPN(String[] tokens) {Stack stack = new Stack();for(int i = 0;i < tokens.length;i++){String s = tokens[i];if(!doNum(s)){stack.push(Integer.valueOf(s));} else {int a = (int)stack.pop();int b = (int)stack.pop();switch(s){case "+":stack.push(b + a); break;case "-":stack.push(b - a); break;case "*":stack.push(b * a); break;case "/":stack.push(b / a); break;}}}return (int)stack.pop();}public boolean doNum(String s){if(s.equals("+") || s.equals("-") || s.equals("*") || s.equals("/")){return true;}return false;}
}

二、常考面试题-单调栈

"他向远方望去,无法看到高山背后的矮山,只能看到一座座更高的山峰。"

(引用灵神的话~)

我们上面已经练习了一部分栈的习题,那么关于这里的单调栈又是什么呢?其实和名字一样

单调栈:要求从栈的一端,到栈的另一端,元素必须是呈单调递增或单调递减的~

而单调栈分为两种,一种是单调递增栈,一种是单调递减栈~

📕 单调递增栈:从栈低到栈顶,元素单调递增的栈

📕 单调递减栈:从栈低到栈顶,元素单调递减的栈

我们可以举一个例子来看一下,如何通过一组数据来获得我们的单调栈

图解(该栈为单调递减栈)

那么单调栈应该运用于哪些场景呢?让我们做几道例题试试~

第一题、商品折扣后的最终价格

📚 思路提示

我们分析一下这道题,我们主要需要做的就是"找出当前价格后的第一个小于它的价格"并且找到我们需要的较小价格后,就可以将当前价格折扣后的价格计算出来了,也就是说可以将它舍弃掉了(也就是出栈~)

那么可以将价格数组 prices 进行遍历,使用一个辅助栈来将未被解决的价格(的下标)存入栈中。

然后继续遍历与压栈直到遇到比目前栈顶价格更小的价格(也就是目标的折扣),就可以算出目前栈顶价格的折扣价格,并将其出栈操作~

(可能目前的 prices[i] 价格也小于栈顶下一个价格,所以此处应用 "while" 而不是 "if")

最后将栈中剩余未被解决的价格,直接原价出售即可~

怎么样,很明显,这也是一道单调栈问题吧~其实单调栈的辨识度还是很高的,只要题中要求你找到一个比一个数值更高/低的元素,并且分析后可以省略之前元素。那么它大概率就是一个单调栈问题~

图解

📖 代码示例

class Solution {public int[] finalPrices(int[] prices) {Deque<Integer> deque = new ArrayDeque<>();int len = prices.length;int[] arr = new int[len];for(int i = 0;i < len;i++){int price = prices[i];while(!deque.isEmpty() && price <= prices[deque.peek()]){int index = deque.pop();arr[index] = prices[index] - prices[i];}deque.push(i);arr[i] = prices[i];}return arr;}
}

第二题、每日温度

📚 思路提示

这题的主要思路与上一题还是基本一致的~只不过上一题要找的是越来越小的元素(单调递增栈),而我们这题需要找的是越来越大的元素(单调递减栈)

一样的思路,遍历温度数组,将未被解决的温度存入栈中(下标)。

如果找到比栈顶温度更高的温度,则计算出栈顶温度与该温度相差天数,随后将已得出结果的栈顶温度出栈,将目前温度入栈

最后栈中未被解决的温度,将它们的天数用0代替。

图解

📖 代码示例

class Solution {public int[] dailyTemperatures(int[] temperatures) {int len = temperatures.length;int[] arr = new int[len];Deque<Integer> stack = new ArrayDeque<>();stack.push(0);for(int i = 0;i < len;i++){int num = temperatures[i];while(!stack.isEmpty() && num > temperatures[stack.peek()]){int index = stack.pop();arr[index] = i - index;}stack.push(i);}return arr;}
}

第三题、股票价格跨度

📚 思路提示

而题中要求我们将传入的数据,一个个往前进行比较,如果前一天的价格小于今天的价格,那么"入栈的股票价格跨度" += "栈顶元素的股票价格跨度",并且前一天的前一天也可能小于今天得价格,所以还是while而不是if~(那么这就是一个单调递减栈)

前两道题让我们完成的是一个"方法"而这道题是想让我们设计实现一个"类"

前两道题给我们的是一个数组,我们就对数组进行一个遍历,然后通过每一步遍历,同时检查并删改栈顶元素即可,而这题我们自己输入元素,而并非给我们一个数组。

而对于这一点不同,我们的解题方法需要有什么修改呢?让我们思考一下

之前我们的栈内只存储了一个元素(下标),这是因为我们的数据值在题中所给出的数组中存储着~而当题目并没有给我们数组,而是让我们自己输入数据时,我们的栈便不能只存储一个数据了,而是必须将每日的股票价格与跨度同时存储,所以我们创建的栈应该是<int[]>类型的~

需要注意的是

📕 由于同时存储了跨度,当对不满足单调性的数据进行出栈时,我们仅仅是想在后续访问过程中忽略该结点的股票价格,而忽略并不代表消失,所以即便后续访问时跳过它,但它的天数仍是做数的!

📕 所以出栈时,我们需要同时对 "入栈的股票价格跨度" += "栈顶元素的股票价格跨度",这不仅仅是因为我们要求出该天的跨度,也是为了后续股票访问到这里时,也一并算上被省略的天数!

图解

📖 代码示例

class StockSpanner {Deque<int[]> stack;public StockSpanner() {stack = new ArrayDeque<int[]>();}public int next(int price) {int day = 1;while(!stack.isEmpty() && stack.peek()[1] <= price){day += stack.pop()[0];}stack.push(new int[] {day,price});return day;}
}

那么这次关于栈与队列(常考面试题)以及(单调栈)的知识,就为大家分享到这里啦,作者能力有限,如果有讲得不清晰或者不正确的地方,还请大家在评论区多多指出,我也会虚心学习的!那我们下次再见哦

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

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

相关文章

【Docker】Docker部署多种容器

关于docker&#xff0c;Windows上使用Powershell/CMD执行指令&#xff0c;Linux系统直接使用终端执行指令。 docker安装MySQL 拉取MySQL 也可以跳过拉取步骤&#xff0c;直接run&#xff0c;这样本地容器不存在的话&#xff0c;会自动拉取最新/指定的版本。 # 默认拉取最新…

Apache Hop从入门到精通 第二课 Apache Hop 核心概念/术语

1、apache hop核心概念思维导图 虽然apache hop是kettle的一个分支&#xff0c;但是它的概念和kettle还是有一些区别的&#xff0c;下图是我根据官方文档梳理的appache hop的核心概念思维导图。 2、Tools&#xff08;工具&#xff09; 1&#xff09;Hop Conf Hop Conf 是一个…

不同音频振幅dBFS计算方法

1. 振幅的基本概念 振幅是描述音频信号强度的一个重要参数。它通常表示为信号的幅度值&#xff0c;幅度越大&#xff0c;声音听起来就越响。为了更好地理解和处理音频信号&#xff0c;通常会将振幅转换为分贝&#xff08;dB&#xff09;单位。分贝是一个对数单位&#xff0c;能…

Apache JMeter 压力测试使用说明

文章目录 一、 安装步骤步骤一 下载相关的包步骤二 安装 Jmeter步骤三 设置 Jmeter 工具语言类型为中文 二、使用工具2.1 创建测试任务步骤一 创建线程组步骤二 创建 HTTP 请求 2.2 配置 HTTP 默认参数添加 HTTP消息头管理器HTTP请求默认值 2.3 添加 查看结果监听器2.4 查看结果…

在 Safari 浏览器中,快速将页面恢复到 100% 缩放(也就是默认尺寸)Command (⌘) + 0 (零)

在 Safari 浏览器中&#xff0c;没有一个专门的快捷键可以将页面恢复到默认的缩放比例。 但是&#xff0c;你可以使用以下两种方法快速将页面恢复到 100% 缩放&#xff08;也就是默认尺寸&#xff09;&#xff1a; 方法一&#xff1a;使用快捷键 (最常用) Command (⌘) 0 (零…

Android Dex VMP 动态加载加密指令流

版权归作者所有&#xff0c;如有转发&#xff0c;请注明文章出处&#xff1a;https://cyrus-studio.github.io/blog/ 上一篇【详解如何自定义 Android Dex VMP 保护壳】实现了 VMP 保护壳。 为了进一步加强对 dex 指令的保护&#xff0c;实现指令流加密和动态加载&#xff0c;…

RabbitMQ故障全解析:消费、消息及日常报错处理与集群修复

文章目录 前言&#xff1a;1 消费慢2 消息丢失3 消息重复消费4 日常报错及解决4.1 报错“error in config file “/etc/rabbitmq/rabbitmq.config” (none): no ending found”4.2 生产者发送消息报错4.3 浏览器打开IP地址&#xff0c;无法访问 RabbitMQ&#xff08;白屏没有结…

Windows图形界面(GUI)-QT-C/C++ - QT控件创建管理初始化

公开视频 -> 链接点击跳转公开课程博客首页 -> ​​​链接点击跳转博客主页 目录 控件创建 包含对应控件类型头文件 实例化控件类对象 控件设置 设置父控件 设置窗口标题 设置控件大小 设置控件坐标 设置文本颜色和背景颜色 控件排版 垂直布局 QVBoxLayout …

Java Web开发进阶——错误处理与日志管理

错误处理和日志管理是任何生产环境中不可或缺的一部分。在 Spring Boot 中&#xff0c;合理的错误处理机制不仅能够提升用户体验&#xff0c;还能帮助开发者快速定位问题&#xff1b;而有效的日志管理能够帮助团队监控应用运行状态&#xff0c;及时发现和解决问题。 1. 常见错误…

B+树的原理及实现

文章目录 B树的原理及实现一、引言二、B树的特性1、结构特点2、节点类型3、阶数 三、B树的Java实现1、节点实现2、B树操作2.1、搜索2.2、插入2.3、删除2.4、遍历 3、B树的Java实现示例 四、总结 B树的原理及实现 一、引言 B树是一种基于B树的树形数据结构&#xff0c;它在数据…

基于springboot的疫情网课管理系统

作者&#xff1a;学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等 文末获取“源码数据库万字文档PPT”&#xff0c;支持远程部署调试、运行安装。 项目包含&#xff1a; 完整源码数据库功能演示视频万字文档PPT 项目编码&#xff1…

android framework.jar 在应用中使用

在开发APP中&#xff0c;有时会使用系统提供的framework.jar 来替代 android.jar, 在gradle中配置如下&#xff1a; 放置framework.jar 依赖配置 3 优先级配置 gradle.projectsEvaluated {tasks.withType(JavaCompile) {Set<File> fileSet options.bootstrapClasspat…

如何将 sqlserver 数据迁移到 mysql

文章目录 前言一、导出SQL Server 数据二、转换数据格式为MySQL兼容格式三、导入数据到MySQL数据库五、使用ETL工具六、通过 navicat 工具七、总结 前言 将 SQL Server 数据迁移到 MySQL 是一个常见的数据库迁移任务&#xff0c;通常涉及以下几个关键步骤&#xff1a;导出 SQL…

GitLab CI/CD使用runner实现自动化部署前端Vue2 后端.Net 7 Zr.Admin项目

1、查看gitlab版本 建议安装的runner版本和gitlab保持一致 2、查找runner 执行 yum list gitlab-runner --showduplicates | sort -r 找到符合gitlab版本的runner&#xff0c;我这里选择 14.9.1版本 如果执行出现找不到下载源&#xff0c;添加官方仓库 执行 curl -L &quo…

56_多级缓存实现

1.查询Tomcat 拿到商品id后,本应去缓存中查询商品信息,不过目前我们还未建立Nginx、Redis缓存。因此,这里我们先根据商品id去Tomcat查询商品信息。此时商品查询功能的架构如下图所示。 需要注意的是,我们的OpenResty是在虚拟机,Tomcat是在macOS系统(或Windows系统)上,…

【STM32-学习笔记-9-】SPI通信

文章目录 SPI通信Ⅰ、SPI通信概述1、SPI技术规格2、SPI应用 3、硬件电路移位示意图 Ⅱ、SPI时序基本单元①、起始条件②、终止条件③、交换一个字节&#xff08;模式0&#xff09;④、交换一个字节&#xff08;模式1&#xff09;⑤、交换一个字节&#xff08;模式2&#xff09;…

小米vela系统(基于开源nuttx内核)——如何使用信号量进行PV操作

如何使用信号量进行PV操作 前言信号量1. 信号量简介2. NuttX中信号量的创建与使用2.1 Nuttx信号量的初始化和销毁2.2 信号量的等待和发布 3. 信号量的实际应用&#xff1a;下载任务示例3.1 实际代码3.2 代码说明3.3 执行说明 4. 信号量的优势与应用场景5. 常见应用场景&#xf…

MySQL Binlog 同步工具go-mysql-transfer Lua模块使用说明

一、go-mysql-transfer go-mysql-transfer是一款MySQL实时、增量数据同步工具。能够实时解析MySQL二进制日志binlog&#xff0c;并生成指定格式的消息&#xff0c;同步到接收端。 go-mysql-transfer具有如下特点&#xff1a; 1、不依赖其它组件&#xff0c;一键部署 2、集成多种…

灌区闸门自动化控制系统-精准渠道量测水-灌区现代化建设

项目背景 本项目聚焦于黑龙江某一灌区的现代化改造工程&#xff0c;该灌区覆盖广阔&#xff0c;灌溉面积高达7.5万亩&#xff0c;地域上跨越6个乡镇及涵盖17个村庄。项目核心在于通过全面的信息化建设&#xff0c;强力推动节水灌溉措施的实施&#xff0c;旨在显著提升农业用水的…

vue2修改表单只提交被修改的数据的字段传给后端接口

效果&#xff1a; 步骤一、 vue2修改表单提交的时候&#xff0c;只将修改的数据的字段传给后端接口&#xff0c;没有修改得数据不传参给接口。 在 data 对象中添加一个新的属性&#xff0c;用于存储初始表单数据的副本&#xff0c;与当前表单数据进行比较&#xff0c;找出哪些…