单调栈(一)

article/2025/9/30 22:11:31

单调栈基本概念及实现


方案1:对于每一个数,遍历其左右位置,时间复杂度为O(N^2)
方案2:单调栈,每个元素入栈一次出栈一次,时间复杂度为O(N)

(一)数组中没有重复值
示例:[3, 4, 2, 6, 1, 7, 0]

  • 准备一个单调栈,栈中记录索引,对应值从栈底到栈顶,由小到大排列。
  • 准备一个二维数组,记录每个位置左边离它最近且比它小的数和右边离它最近且比它小的数
  • 当前元素准备入栈时,如果栈顶元素大于当前入栈元素需要弹出栈顶元素直到栈顶元素小于入栈元素,维持栈的单调性
  • 每当有元素弹栈时,需要记录相关信息,使它弹栈的元素是其右边离它最近且比它小的元素,弹栈之后栈底元素是其左边最近且比它小的元素
  • 当所有元素入栈之后,若栈不为空,依次弹栈,由于是主动弹栈,所以不存在右边最近且小的元素。
//数组中没有重复元素public static int[][] getNearLessNoRepeat(int[] arr) {int[][] ret =  new int[arr.length][2];Stack<Integer> stack = new Stack<>();for (int i = 0; i < arr.length; i++) {while(!stack.isEmpty() && arr[stack.peek()] > arr[i]){//弹栈并记录相关信息int index = stack.pop();ret[index][1] = i;//记录右边离index最近且小的元素ret[index][0] = stack.isEmpty() ? -1 : stack.peek();//记录左边离index最近且小的元素}stack.push(i);}while(!stack.isEmpty()){int index = stack.pop();ret[index][1] = -1;//没有右边最近且小的元素ret[index][0] = stack.isEmpty() ? -1 : stack.peek();}return ret;}

(二)数组中有重复值

  • 准备一个单调栈,栈中记录索引(List),对应值从栈底到栈顶,由小到大排列。
  • 准备一个二维数组,记录每个位置左边离它最近且比它小的数和右边离它最近且比它小的数
  • 当前元素准备入栈时,如果栈顶元素大于当前入栈元素需要弹出栈顶元素直到栈顶元素小于入栈元素,维持栈的单调性
  • 如果栈顶元素等于当前入栈元素,将当前元素索引添加到栈顶索引集合的尾部(使用ArrayList)
  • 每当有元素弹栈时,需要记录相关信息,使它弹栈的元素是其右边离它最近且比它小的元素,弹栈之后栈底元素是其左边最近且比它小的元素
  • 当所有元素入栈之后,若栈不为空,依次弹栈,由于是主动弹栈,所以不存在右边最近且小的元素。
//数组中有重复值public static int[][] getNearLess(int[] arr) {int[][] res = new int[arr.length][2];Stack<List<Integer>> stack = new Stack<>();for (int i = 0; i < arr.length; i++) {while(!stack.isEmpty() && arr[stack.peek().get(0)] > arr[i]){List<Integer> list = stack.pop();//左边最近且小的元素为栈顶集合最后一个索引Integer left = stack.isEmpty() ? -1 : stack.peek().get(stack.peek().size()-1);for (Integer integer : list) {res[integer][0] = left;res[integer][1] = i;}}//判断当前栈顶值是否等于待入栈元素的值if(!stack.isEmpty() && arr[stack.peek().get(0)] == arr[i]){stack.peek().add(i);}else {ArrayList<Integer> list = new ArrayList<>();list.add(i);stack.push(list);}}while(!stack.isEmpty()){List<Integer> pop = stack.pop();Integer left = stack.isEmpty() ? -1 : stack.peek().get(stack.peek().size()-1);for (Integer integer : pop) {res[integer][0] = left;res[integer][1] = -1;}}return res;}

题目一:子数组的累加和*子数组的最小值

在这里插入图片描述
解法一:暴力解法

 //暴力解法,时间复杂O(N^3)public static int method(int[] arr){int ans = Integer.MIN_VALUE;//找出所有子数组for (int i = 0; i < arr.length; i++) {for (int j = i; j < arr.length; j++) {//遍历所有子数组,计算累加和同时找出最小值int min = arr[i];int sum = 0;for(int k = i; k <= j; k++){min = Math.min(arr[k],min);sum += arr[k];}ans = Math.max(ans, min * sum);}}return ans;}

解法二:单调栈
思路:

  1. 分别找出以i位置的值作为最小值的所有子数组
  2. 在保证i位置的值作为最小值的前提下,使子数组的累加和最大:找到i左边最近且比它小的索引left,找到i右边最近且比它小的索引right
  3. (left…i…right)开区间内的累加和最大
  4. 虽然数组中有重复值,但是栈中可以不用存放列表。如果当前栈顶元素等于待入栈元素,直接弹出相同元素。实际上弹出相同元素时,我们并没有找到其右边最近且小的元素,计算的结果是错误的。但我们并不严格要求每个位置都计算正确,因为相同元素之间实际上是联通的,我们并不会错过正确的结果。
  5. 每次弹出元素后,就计算累加和*最小值

因为要计算累加和,所以要预处理前缀和数组
假设原数组arr[3, 2, 1, 2, 4, 5]
预处理前缀和数组为 pre[3, 5, 6, 8, 12, 17], pre[i]代表原数组0-i的累加和
(i-j)累加和 = (0-j)累加和 - (0-i-1)累加和

public static int method1(int[] arr){//预处理前缀和数组int[] sum = new int[arr.length];sum[0] = arr[0];for(int i = 1; i < arr.length; i++){sum[i] = sum[i-1] + arr[i];}Stack<Integer> stack = new Stack<>();int ret = Integer.MIN_VALUE;for (int i = 0; i < arr.length; i++) {while(!stack.isEmpty() && arr[stack.peek()] >= arr[i]){Integer pop = stack.pop();//以pop位置的值作为最小值int right = i;//右边最近且小int left = stack.isEmpty() ? -1 : stack.peek();//左边最近且小int total = left == -1 ? sum[right-1] : sum[right-1] - sum[left];ret = Math.max(ret, arr[pop] * total);}stack.push(i);}while(!stack.isEmpty()){Integer pop = stack.pop();int left = stack.isEmpty() ? -1 : stack.peek();int total = left == -1 ? sum[pop] : sum[pop] - sum[left];ret = Math.max(ret, total * arr[pop]);}return ret;}

题目二:直方图的最大长方形面积

在这里插入图片描述
思路分析:计算以i位置的值为高的长方形的最大面积
自己手动实现栈,效率更高

class Solution {public int largestRectangleArea(int[] heights) {int ans = Integer.MIN_VALUE;int top = -1;//栈顶指针指向当前栈顶元素int[] stack = new int[heights.length];for(int i = 0; i < heights.length; i++){//栈顶元素与当前待入栈元素相等时直接弹出,相同元素具有连通性while(top != -1 && heights[stack[top]] >= heights[i]){int pop = stack[top--];//以当前弹出元素为高int right = i;int left = top == -1 ? -1 : stack[top];int area = left == -1 ? (heights[pop]* right) : (heights[pop] * (right-1-left));ans = Math.max(ans, area);}stack[++top] = i;}while(top != -1){int pop = stack[top--];int right = -1;int left = top == -1 ? -1 : stack[top];int area = left == -1 ? heights[pop] * heights.length : heights[pop] * (heights.length - 1 - left);ans = Math.max(ans, area);}return ans;}
}

题目三:最大矩形

在这里插入图片描述
思路分析:

  1. 把二维矩阵每一行当作直方图,计算每一个位置的高
  2. 每一行作地基,计算对应直方图数组中最大矩形
  3. 直方图的高取决于1的个数
class Solution {public int maximalRectangle(char[][] matrix) {int[] arr = new int[matrix[0].length];int ans = Integer.MIN_VALUE;int[] stack = new int[arr.length];int top = -1;for(int i = 0; i < matrix.length; i++){for(int j = 0; j < matrix[0].length; j++){if(matrix[i][j] == '0'){arr[j] = 0;}else{arr[j] += 1;}while(top != -1 && arr[stack[top]] >= arr[j]){int pop = stack[top--];int right = j;int left = top == -1 ? -1 :  stack[top];int total = left == -1 ? (arr[pop] * right) : (arr[pop] * (right-1-left));ans = Math.max(ans, total);}stack[++top] = j;}while(top != -1){int pop = stack[top--];int right = -1;int left = top == -1 ? -1 : stack[top];int total = left == -1 ? (arr[pop] * arr.length) : (arr[pop] * (arr.length-1-left));ans = Math.max(ans ,total);}}return ans;}
}

题目四:统计全1子矩阵

在这里插入图片描述
思路分析:

  1. 每一行作地基,计算直方图中子矩阵的数量
  2. 计算以i位置的值为高的子矩阵的数量
    在这里插入图片描述
class Solution {public int numSubmat(int[][] mat) {int ans = 0;int arr[] = new int[mat[0].length];//直方图数组int top = -1;int[] stack = new int[arr.length];for(int i = 0; i < mat.length; i++){for(int j = 0; j < mat[0].length; j++){if(mat[i][j] == 0){arr[j] = 0;}else{arr[j] += 1;}while(top!=-1 && arr[stack[top]] >= arr[j]){int pop = stack[top--];int right = j;int left = top == -1 ? -1 : stack[top];int low = left == -1 ? arr[right] + 1 : Math.max(arr[left],arr[right])+1;int high = arr[pop];int n = left == -1 ? right : (right - 1 -left);ans += (high-low+1)*(n+1)*n/2;}stack[++top] = j;}while(top!=-1){int pop = stack[top--];int right = -1;int left = top == -1 ? -1 : stack[top];int high = arr[pop];int low = left == -1 ? 1 : arr[left]+1;int n = left == -1 ? arr.length : arr.length-1-left;ans += (high-low+1)*n*(n+1)/2;}}return ans;}
}

题目五:返回所有子数组中最小值的累加和(Leetcode907)

在这里插入图片描述
分析思路:
寻找以i位置的值作为最小值的所有子数组个数,由于数组中有重复元素,所以要考虑去重。
去重方案一:

  1. 寻找i左边最近“小于等于”arr[i]的数的位置 left
  2. 寻找i右边最近“严格小于”arr[i]的数的位置 right
  3. 子数组个数:(i-left) * (right-i)
  4. 元素入栈时,如果栈顶元素大于当前待入栈元素,则弹出栈顶元素直到栈顶元素小于等于待入栈元素

去重方案二:
5. 寻找i左边最近“严格小于”arr[i]的数的位置left
6. 寻找i右边最近“小于等于”arr[i]的数的位置right
7. 子数组个数: (i-left)*(right-i)
8. 元素入栈时,如果栈顶元素大于等于当前待入栈元素,则弹出栈顶元素直到栈顶元素严格小于当前待入栈元素

class Solution {public int sumSubarrayMins(int[] arr) {int[] stack = new int[arr.length];int top = -1;long sum = 0;for(int i = 0; i < arr.length; i++){while(top != -1 && arr[stack[top]] > arr[i]){int pop = stack[top--];int right = i;//右边严格小于int left = top ==-1 ? -1 : stack[top];//左边小于等于sum += (long)arr[pop] * (long)(left == -1 ? (pop+1) * (right-pop) : (pop - left)*(right-pop));sum = sum % (1000000000+7);}stack[++top] = i;}while(top != -1){int pop = stack[top--];int right = -1;int left = top == -1 ? -1 : stack[top];sum += (long)arr[pop] *(long)(left == -1 ? (pop + 1)*(arr.length-pop) : (pop-left)*(arr.length-pop));sum = sum % (1000000000+7);}return (int)sum%(1000000000+7);}
}

http://chatgpt.dhexx.cn/article/rJDwbQfx.shtml

相关文章

第九章:单调栈与单调队列

单调栈与单调队列 一、单调栈1、什么是单调栈&#xff1f;2、单调栈的模板&#xff08;1&#xff09;问题&#xff1a;&#xff08;2&#xff09;分析&#xff1a; 二、单调队列1、什么是单调队列2、单调队列模板&#xff08;1&#xff09;问题&#xff08;2&#xff09;分析 一…

单调栈算法详解

单调栈算法详解 单调栈使用模板 stack<int> st; //此处一般需要给数组最后添加结束标志符&#xff0c;具体下面例题会有详细讲解 for (遍历这个数组){if (栈空 || 栈顶元素大于等于当前比较元素){入栈;}else{while (栈不为空 && 栈顶元素小于当前元素){栈顶元素…

单调队列和单调栈详解

这里是我的blog&#xff1a;有更多算法分享。排版可能也会更好看一点v https://endlesslethe.com/monotone-queue-and-stack-tutorial.html 前言 单调栈和单调队列算是栈和队列的高级应用吧&#xff0c;在公司面试中应该是不怎么会出现的&#xff08;除非算法岗&#xff1f;…

什么是单调栈

什么是单调栈 单调栈就是单调递增或者单调递减的栈&#xff0c;也就是栈底到栈顶递增或递减&#xff0c;根据单调栈的的这种结构&#xff0c;可以很容易想到运用单调栈可以很容易的把O(n)的时间复杂度优化到O(n),如果使用数组的话&#xff0c;相对的空间复杂度也不会太高 示例 …

Java实现之单调栈

目录 一.单调栈 二.每日温度 1.题目描述 2.问题分析 3.代码实现 三.下一个更大元素 I 1.题目描述 2.问题分析 3.代码实现 四.下一个更大元素 II 1.题目描述 2.问题分析 3.代码实现 一.单调栈 通常是一维数组&#xff0c;要寻找任一个元素的右边或者左边第一个比自…

[数据结构]单调栈

单调栈 这是笔者的第一篇博客&#xff0c;由于笔者自身水平的限制。用词可能不够准确&#xff0c;句子不太通顺&#xff0c;代码水平可能也不太行&#xff0c;敬请指出&#xff0c;感激不尽&#xff01; 我们都知道栈&#xff08;Stack&#xff09;是一种先入后出的数据结构&am…

单调栈和单调队列

本文摘自博客&#xff0c;欢迎前往博客以获得更好的体验。 单调栈 从名字上就听的出来&#xff0c;单调栈中存放的数据应该是严格单调有序的&#xff0c;具有以下两个性质。 满足从栈顶到栈底的元素具有严格的单调递增或单调递减性&#xff1b;满足栈的后进先出特性&#xff…

数据结构之单调栈(含代码实现)

目录 1.单调栈的基本概念 &#xff1a; 2.单调栈的应用 2.1单调栈 2.2单调栈进阶 2.3最大矩形面积 2.4最大矩形 2.5统计全为1的子矩阵数量 ​ 1.单调栈的基本概念 &#xff1a; 相信大家对栈都非常的熟悉&#xff1f;栈有一个非常鲜明的特点&#xff1a;先进后出 而所谓 单调栈…

C++之单调栈

单调栈的性质 单调栈是一种特殊的栈&#xff0c;特殊之处在于栈内的元素都保持一个单调性。 假设下图是一个栈内元素的排列情况(单调递增的栈)&#xff1a; 此时插入情况有两种&#xff1a; &#xff08;1&#xff09;插入元素大于栈顶元素&#xff1a; 因为7 > 6&#xf…

单调栈以及单调栈的应用

文章目录 单调栈的概念单调栈的应用CD101 单调栈结构&#xff08;无重复值&#xff09;CD188 单调栈结构(有重复值)496. 下一个更大元素 I739. 每日温度1856. 子数组最小乘积的最大值84. 柱状图中最大的矩形85. 最大矩形1504. 统计全 1 子矩形907. 子数组的最小值之和1307 验证…

单调栈完全解析

目录 单调栈的应用场景 为什么要使用单调栈&#xff1f; 单调栈作用的基本过程 单调栈的实现方式 栈里面的元素存放数字下标&#xff08;无重复元素&#xff09; 栈里面的元素存放数字下标组成的链表 &#xff08;有重复元素&#xff09; 单调栈的应用题目 直方图类型 …

单调栈与单调队列

文章目录 单调栈与单调队列一、单调栈1.单调递增栈2.单调递减栈总结 二、单调队列(单调双端队列) 单调栈与单调队列总结&#xff1a; 单调栈与单调队列 单调栈就是栈内元素满足单调性的栈结构。此处的单调性分为单调递增与单调递减 如何维护一个单调栈&#xff1a; 单调递增栈…

详解单调栈算法

前言 嘿&#xff01;彩蛋&#xff01;感觉有帮助就三连呗&#xff01; 如果你对这篇文章可感兴趣&#xff0c;可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」&#xff0c;查看完整博客分类与对应链接。 栈属于基础数据结构之一&#xff0c;基础到仅用「后进…

单调栈

定义&#xff1a; 单调栈&#xff0c;顾名思义就是栈内元素单调按照递增(递减)顺序排列的栈。 单调递增栈&#xff1a; ①在一个队列中针对每一个元素从它右边寻找第一个比它小的元素 ②在一个队列中针对每一个元素从它左边寻找第一个比它小的元素 单调递减栈&#xff1a; …

单调栈(C/C++)

目录 1. 单调栈的定义 2. 单调栈的常见用途 3. 案例分析 3.1 暴力解法 3.2 单调栈 4. 单调栈总结 1. 单调栈的定义 单调栈顾名思义&#xff0c;就是栈内的元素是单调的。根据栈内元素的单调性的不同&#xff0c;可以分为&#xff1a; 单调递增栈&#xff1a;栈内元素是单…

【算法】单调栈

目录 单调栈的定义&#xff1a;伪代码&#xff1a;应用1.模板题2.视野总和问题3.柱状图中的最大矩形4.最大区间 碎碎念&#xff1a; 单调栈的定义&#xff1a; 从名字上就能猜出来&#xff0c;这种数据结构在栈的基础上&#xff0c;栈内的元素是单调有序的&#xff0c;所以单调…

单调栈详解

定义&#xff1a; 单调栈&#xff0c;顾名思义就是栈内元素单调按照递增(递减)顺序排列的栈。 适用问题&#xff1a; 要知道单调栈的适用于解决什么样的问题&#xff0c;我们首先需要知道单调栈的作用。单调栈分为单调递增栈和单调递减栈&#xff0c;通过使用单调栈我们可以访…

[数据结构]——单调栈

单调栈 笔者在做leetcode的题(下一个出现的最大数字)时&#xff0c;接触到了单调栈这一种数据结构&#xff0c;经过研究之后&#xff0c;发现单调栈在解决某些问题时出奇的好用&#xff0c;下面是对单调栈的性质和一些典型题目。 什么是单调栈&#xff1f; 从名字上就听的出…

scp出现错误的解决办法

scp往远程主机上传送rmandata时报了如下错误&#xff1a; (看不见的放大) 我的做法是&#xff1a; 在执行scp的主机提示的scp目录 vi ~/.ssh/known_hosts 删除我红色部分遮盖的主机ip那一行&#xff0c;故障解决 来自 “ ITPUB博客 ” &#xff0c;链接&#xff1a;http://blo…

关于 fatal error LNK1158: 无法运行“rc.exe” 的解决方法

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://blog.csdn.net/qq21497936/article/details/110680001 各位读者&#xff0c;知识无穷而人力有穷&#xff0c;要么改需求&#xff0c;要么找专业人士&#xff0c;要么自己研究 红胖子(红模仿…