单调栈在解决问题中的应用

357 阅读3分钟

单调栈的应用

单调栈分为单调递增栈 和单调递减栈

单调递增就是后入栈的元素比栈内元素大, 如果将要入栈的元素比栈顶元素小 就将栈顶元素踢出去 再判断一直到满足条件

单调递减和递增相反 后入栈的元素比栈内元素小

效果

比如递增栈,他的每次出栈元素,都能找到该元素上一个比他小的元素,以及下面第一个比他小的元素

比如递减栈, 每个出栈元素,能找到左边第一个比他大的元素,和右边第一个比他大的元素

常用的环境

解决的问题 比如 leetcode 中 #42 和#84

例如:

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

示例 1:img

输入:height = [0,1,0,2,1,0,1,3,2,1,2,1] 输出:6 解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 示例 2:

输入:height = [4,2,0,3,2,5] 输出:9

题解: 接到的雨水 就是每个柱子找到左边第一个比他大的柱子和右边第一个比他大的柱子,通过两边的高柱子之间的距离和相对低的柱子和当前柱子的差来计算能接到的雨水,然后将所有接到的水相加

/**
 * @param {number[]} height
 * @return {number}
 */
var trap = function(height) {
  let sum = 0;
  let stack = [];  // 这个栈是递减的栈
  let current = 0  // 声明一个索引 和height 的length 比较,确保每个柱子都被遍历
  while(current<height.length) {
      // 如果栈中有值 并且 当前遍历的值 大于栈顶的值 那进入循环 将栈顶的值弹出 并计算弹出的值能接到的雨水
    while(stack.length>0&&height[current]>=height[stack[stack.length-1]]) {
        // 弹出栈顶的值
      let index = stack.pop();
       // 如果弹出栈顶的值后 栈为空,说明弹出值的前面没有比他更大的值,所以他接不到水
      if (stack.length==0) {
        break
      }
      // 计算弹出的值 与他左右两边第一个比他大的值 构成的距离
      let d = current-stack[stack.length-1]-1
      // 计算左右两边第一个比他大的值的相对较小的值
      let h = Math.min(height[current],height[stack[stack.length-1]])
      // 计算能接到的水
      sum += d*(h-height[index])

    }
    stack.push(current)
    current++
  }
  return sum
};

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

求在该柱状图中,能够勾勒出来的矩形的最大面积。

img

以上是柱状图的示例,其中每个柱子的宽度为 1,给定的高度为 [2,1,5,6,2,3]。图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10 个单位。

示例:

输入: [2,1,5,6,2,3] 输出: 10

题解 : 计算面积最大的矩形,就是计算每个柱子高度能够得到的矩形做比较,取最大值。每个矩形的面积等于当前柱子的高度乘以横向的长度, 横向的长度就是找到当前柱子左边第一个比他小的柱子 和右边第一个比他小的柱子。 这个用单调递增栈来解决

/**
 * @param {number[]} heights
 * @return {number}
 */
var largestRectangleArea = function(heights) {
    let stack = []; // 定义一个单调递增栈
    heights.push(-1) // 
    let maxans = 0;
    for (let i=0;i<heights.length;i++) {
        let cur = heights[i]
        // 栈不为空 并且当前柱子小于栈顶的柱子高度 进入循环
        while(stack.length>0&&cur<heights[stack[stack.length-1]]) {
            // 弹出栈顶的索引值
            let index = stack.pop()
            // 计算弹出的柱子 和他左边第一个比他小的柱子之间的距离  如果栈为空说明当前弹出的柱子左边的柱子都比他要高,如果不为空,那就找到与新栈顶的距离
            let left = stack.length==0?index:index-stack[stack.length-1]-1
            // 当前弹出的柱子 与右边比他小的柱子的距离
            let right = i-index-1
            maxans = Math.max(maxans,(left+right+1)*heights[index])
        }
        // 将当前柱子的索引压入栈
        stack.push(i)
    }
    return maxans
};