算法数据结构--单调栈

2024-05-04 03:28
文章标签 数据结构 单调 算法

本文主要是介绍算法数据结构--单调栈,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

    • 介绍
      • 单调递增栈
      • 单调递减栈
      • 图示
      • 应用场景
    • 步骤
    • 模板
    • Deque用法
    • 例题
      • [739. 每日温度](https://leetcode.cn/problems/daily-temperatures/)
      • [496. 下一个更大元素 I](https://leetcode.cn/problems/next-greater-element-i/)
    • 总结

介绍

单调栈是一种特殊的栈数据结构,其特点在于栈内的元素保持单调性。单调栈通常分为单调递增栈和单调递减栈两种类型。

单调递增栈

​ 单调递增栈中的元素从栈底到栈顶依次递增。当我们向单调递增栈中压入一个元素时,如果该元素比栈顶元素大,就直接入栈;如果该元素比栈顶元素小,则不断将栈顶元素出栈,直到栈为空或者栈顶元素小于等于当前元素,然后再将当前元素入栈。单调递增栈主要用于解决一些需要找到右边第一个较大元素的问题。

单调递减栈

​ 单调递减栈中的元素从栈底到栈顶依次递减。当我们向单调递减栈中压入一个元素时,如果该元素比栈顶元素小,就直接入栈;如果该元素比栈顶元素大,则不断将栈顶元素出栈,直到栈为空或者栈顶元素大于等于当前元素,然后再将当前元素入栈。单调递减栈主要用于解决一些需要找到右边第一个较小元素的问题。

图示

在这里插入图片描述

应用场景

单调栈在解决一些数组或序列相关问题时非常有效。

例如:

  • 寻找数组中下一个较大或较小元素;
  • 计算滑动窗口的最大值或最小值;
  • 解决一些与序列相关的动态规划问题;
  • 解决一些需要找到数组中局部极值或单调性的问题等。

步骤

单调栈的步骤一般分为:

  1. 定义一个栈

    Deque<Integer> deque=new ArrayDeque<Integer>();
    
  2. 确定栈里存储的数据是什么

    一般情况下单调栈里面存放的都是目标数组的下标,这样做的好处就是,我们不仅知道遍历过的元素下标,而且也可以通过下标找到对应数组里面的元素。

  3. 确定使用单调递增栈还是单调递减栈

    如果求一个元素右边第一个更大元素时,单调栈就是递增的,如果求一个元素右边第一个更小元素,单调栈就是递减的。

  4. 遍历目标数组,比较当前遍历的元素与栈顶元素,并进行处理
    这里在遍历目标数组的时候,需要比较当前遍历元素和栈顶元素的大小。
    这个时候就有如下三种情况:

    • 当前遍历的元素小于栈顶元素的情况
    • 当前遍历的元素等于栈顶元素的情况
    • 当前遍历的元素大于栈顶元素的情况

    一般情况下单调递增栈在当前遍历的元素大于栈顶元素的情况下进行主要的逻辑处理,其他情况下直接把当前元素做入栈操作;而单调递减栈在当前遍历的元素小于栈顶元素的情况下进行主要的逻辑处理,其他情况下直接把当前元素做入栈操作。

    注意:在对栈进行操作的时候,要尽量减少对栈的操作次数。比如,如果在获取栈顶元素后,还要进行弹出操作且再获取栈顶元素后不需要在将该元素保留栈顶。则此时,可以直接使用pop()方法,代替使用peek()方法后再使用pop()方法。

  5. 返回结果

模板

import java.util.*;public class MonotonicStackSolver {// 单调递增栈public void solveIncreasing(int[] nums) {Deque<Integer> stack = new ArrayDeque<>();// 遍历数组for (int i = 0; i < nums.length; i++) {// 维护单调性while (!stack.isEmpty() && nums[i] < stack.peek()) {stack.pop();// 在这里可以执行相关操作}// 入栈stack.push(nums[i]);}}// 单调递减栈public void solveDecreasing(int[] nums) {Deque<Integer> stack = new ArrayDeque<>();// 遍历数组for (int i = 0; i < nums.length; i++) {// 维护单调性while (!stack.isEmpty() && nums[i] > stack.peek()) {stack.pop();// 在这里可以执行相关操作}// 入栈stack.push(nums[i]);}}public static void main(String[] args) {MonotonicStackSolver solver = new MonotonicStackSolver();// 示例用法int[] nums = {2, 5, 9, 3, 1, 12, 6, 8};solver.solveIncreasing(nums); // 使用单调递增栈解决问题// 或者solver.solveDecreasing(nums); // 使用单调递减栈解决问题}
}

Deque用法

我们在用Java写单调栈题目的时候一般需要用到以下这几个方法:

  • deque.isEmpty():如果deque不包含任何元素,则返回true,否则返回false。因为要栈顶元素在满足要求的时候要弹出,所以需要进行空栈判断。有些场景,可能栈一定不会空的时候,就不需要该方法进行空栈判断。
  • deque.push(e):将元素e入栈。
  • deque.pop():将栈顶元素弹出,并返回当前弹出的栈顶元素。
  • deque.peek():获取栈顶元素,不弹出。

例题

下面我们用两个简单的例题来加深我们对单调栈的理解:

739. 每日温度

在这里插入图片描述

本题意思就是让我们去找每一个元素下一个比他更大的元素出现在后面的哪里,如果有则记录下标之差,如果没有则设置为0,最后返回答案数组。

我们看下代码:

class Solution {public int[] dailyTemperatures(int[] temperatures) {int[] ans = new int[temperatures.length];//定义答案数组Deque<Integer> stack = new ArrayDeque<Integer>();//定义一个栈for (int i = 0; i < temperatures.length; i++) {while (!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) {//如果栈不为空且遍历的元素大于栈顶元素,那么就要出栈,并且此时也证明栈顶元素找到了下一个比它更大的元素的位置,记录下标之差就可以了int m = stack.pop();//弹出栈顶元素(下标)ans[m] = i - m;//用此时遍历的元素下标减去栈顶元素下标就是栈顶元素下标下一个比他温度更高的天数}stack.push(i);//其他情况(栈为空或者遍历元素小于等于栈顶元素)都入栈}return ans;//返回答案}
}

我们往栈内存储的元素是下标而不是元素数值,这样我们就可以很容易地求出下标之差。

用Deque比自己创建一个数组来表示栈更方便一点

496. 下一个更大元素 I

在这里插入图片描述

本题就是让我们去找在nums1中出现过的数字在nums2中去找比它大的第一个数字是多少,答案要存储在nums1中的对应下标位置。

本题我们很容易就可以联想到要使用哈希表去存储nums1中出现过的数字,当循环nums2数组的时候就可以直接判断存在不存在了。

下面我们直接来看代码:

class Solution {public int[] nextGreaterElement(int[] nums1, int[] nums2) {int[] hash = new int[10001];for (int i = 0; i < nums1.length; i++) {hash[nums1[i]] = i + 1;//这里可以将哈希表对应下标位置的数值直接设置成i+1,这样在之后记录答案的时候直接在答案数组hash[]-1的位置记录就可以了}Deque<Integer> stack = new ArrayDeque<Integer>();int[] ans = new int[nums1.length];int ansTop = 0;for (int i = 0; i < nums1.length; i++)ans[i] = -1;for (int i = 0; i < nums2.length; i++) {while(!stack.isEmpty()&&nums2[i]>nums2[stack.peek()]){int m = stack.pop();ans[hash[nums2[m]]-1] = nums2[i];}if(hash[nums2[i]]!=0)//遍历的数字在nums1中出现过才能入栈stack.push(i);}while(!stack.isEmpty()){int m = stack.pop();ans[hash[nums2[m]]-1] = -1;//栈内剩余的部分就是后面没有比他大的数字,直接设置成-1就可以了}return ans;}
}

总结

总的来说,单调栈是一种高效、简洁且灵活的数据结构。

单调栈的空间复杂度通常很低,因为它只需要存储输入序列中的一部分元素,而不需要额外的空间。

通常情况下,单调栈的时间复杂度为 O(n),其中 n 为输入序列的长度。这是因为在大多数情况下,每个元素最多进栈一次、出栈一次,所以整个过程的时间复杂度是线性的。

所以单调栈在解决数组或序列相关问题时具有很好的效果。

已经到底啦!!

这篇关于算法数据结构--单调栈的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/958247

相关文章

算法的设计方式

1.贪心算法 贪心算法(又称贪婪算法)是指在对问题求解时,从问题的某一个初始解出发,总是做出在当前看来最好的选择,当达到某算法中的某一步不能再继续前进时,算法停止。这时,就得到了问题的一个解,但不能保证求得的最后解是最优的。也就是说,不从整体最优上加以考虑,它所做出的仅是在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,但对范围相当广泛的许多问题能产生整体最优解或者是整体最优解

冒泡算法及改进(属于交换排序)

冒泡排序(Bubble Sort)是一种交换排序,快速排序也属于一种交换排序。冒泡排序的基本思想是:两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。 假设一共共有 n 个数,则会进行 (n-1)趟比较,由1,2......n-1这么多趟,第一趟进行 (n-1)次比较,.......第n-1趟进行1次比较,故有公式:第i趟 +  第i趟的比较次数 = n       时间复杂度为

算法day07

第一题 30. 串联所有单词的子串         上题题意如下:          将w数组里面的字符串随机排列,只要在s字符串中找到相对应的w组成的字符串,则返回s中对应字符串首位元素的第一个下标;                  有上述题意所知,解题思路如上一题故事,本题采用hash表和滑动窗口的模型;         首先对于words字符串数组进行处理:

【算法】网络图中的dfs

快乐的流畅:个人主页 个人专栏:《算法神殿》《数据结构世界》《进击的C++》 远方有一堆篝火,在为久候之人燃烧! 文章目录 引言一、单词搜索二、黄金矿工三、不同路径 |||四、图像渲染五、岛屿数量六、岛屿的最大面积七、被围绕的区域八、太平洋大西洋水流问题九、扫雷游戏总结 引言 在二维网络图中的dfs,反而一般不需要画决策树,因

算法工程师面试问题 | YOLOv8面试考点原理全解析(一)

本文给大家带来的百面算法工程师是深度学习目标检测YOLOv8面试总结,文章内总结了常见的提问问题,旨在为广大学子模拟出更贴合实际的面试问答场景。在这篇文章中,我们还将介绍一些常见的深度学习目标检测面试问题,并提供参考的回答及其理论基础,以帮助求职者更好地准备面试。通过对这些问题的理解和回答,求职者可以展现出自己的深度学习目标检测领域的专业知识、解决问题的能力以及对实际应用场景的理解。同时,这也是为

Vue原理学习:vdom 和 diff算法(基于snabbdom)

vdom 和 diff 背景 基于组件化,数据驱动视图。只需关心数据,无需关系 DOM ,好事儿。 但是,JS 运行非常快,DOM 操作却非常慢,如何让“数据驱动视图”能快速响应? 引入 vdom 用 vnode 表示真实 DOM 结构  <div id="div1" class="container"><p>vdom</p><ul style="font-size: 20px">

代码随想录算法训练营第五十五天| 583. 两个字符串的删除操作 ,72. 编辑距离

目录 题目链接: 583. 两个字符串的删除操作 思路 代码 题目链接: 72. 编辑距离 思路 代码 总结 题目链接:583. 两个字符串的删除操作 思路         ①dp数组,dp[i][j]表示下标以i-1结尾的word1和下标以j-1结尾的word2若要相等,所需删除元素的最小次数         ②递归公式,当word1[i-1] == word2

LeetCode算法题:15. 三数之和(Java)

题目描述 给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请 你返回所有和为 0 且不重复的三元组。 注意: 答案中不可以包含重复的三元组。 示例 1: 输入:nums = [-1,0,1,2,-1

算法:解码方法(动态规划)—— decode-ways(Dynamic programming)

problem: A string “s” is consisted of number characters. If thesubstring value of one or two adjacent characters in “s” is between 1 and 26,the substring can be converted to an alphabet. The rule i