单调栈的使用

单调栈不是一种数据结构,可以理解成一个算法。

单调栈的能力:

可以使用空间换时间的方法,在时间复杂度为n的情况下,找到一个数组中,每一个数组左侧(或右侧),比该值小(或者大)的最临近的值以及其下标。

比如:

找到 10,3,7,4,12 中每个元素左侧的比其更小值的下标,通常需要循环两次n^2,但现在使用单调栈可以n的时间内完成识别。识别的结果为:-1,-1,1,1,3  其中-1代表左侧没有比其更小的值,正数代表下标。

 

单调递增栈和单调递减栈:

单调递增和单调递减,看的是栈顶到栈底是递增还是递减的。

 

怎么确定该使用哪个单调栈?(结论)

求左侧的更小的值,单调递减栈,数组正向遍历

求左侧的更大的值,单调递增栈,数组正向遍历

求右侧的更小的值,单调递减栈,数组反向遍历

求右侧的更大的值,单调递增栈,数组反向遍历

需要注意的是,如果存在1,1,1这种连续一样的数字,需要单独在做处理。

 

单调栈的原理:

举例:找到10,3,7,4,12中每一个节点的值的左侧的最小的值的位置。

定义一个普通的栈,并将其做成单调减栈,定义另外一个min[]数组,从左向右遍历,如果栈是空的,就把这个值压进栈中,把min[]对应的位置写成-1.    如果栈中有值,且压入的值大于peek栈顶的值,可以构成单调递减栈,因此值的下标入栈,继续遍历。   如果栈中有值,且压入的值小于peek顶的值,peek出栈。  如果栈中有值,且值和栈顶一样,需要根据实际情况判断怎么处理。

实现过程可以参考:https://blog.csdn.net/Zolewit/article/details/88863970

 

对应习题:LeetCode 84  柱状图中最大的矩形

标准答案中用了单调栈,但并不通用,我写了一个通用的方式,虽然看起来大,但很容易理解

package leetCode;

import java.util.Stack;

public class LeetCode_84 {

    public static void main(String[] args) {
        LeetCode_84 leetCode = new LeetCode_84();
        int[] highs = {0, 1, 0, 1};
        int maxVolumn = leetCode.largestRectangleArea(highs);
        System.out.println(maxVolumn);
    }

    /**
     * 非常经典的题目
     * 1.如何找到最大的面积?
     * 从头到尾遍历,以每一个值的高度为该范围的最大高度,左右两侧找高度小于等于它的柱子,找到以它为最高的最大面积
     * 2.找左右两侧最大范围,很容易时间复杂度变成n^2,所以需要用最短的时间找到
     * 使用单调栈的方法,找到每一个节点,最左侧和左右侧的最小值,然后计算该节点的最大体积
     *
     * @param heights 柱状体的高度
     * @return 最大的存水体积
     */
    public int largestRectangleArea(int[] heights) {
        int length = heights.length;

        // 获得当前每个节点,其左侧的最近的比其小的位置
        // 使用正向的 单调递增栈
        int[] leftMin = getLeftMinLocartion(heights);

        // 获得当前每个节点,其右侧的最近的比其小的位置
        // 使用反向的 单调递减栈
        int[] rightMin = getRightMinLocartion(heights);

        // 计算体积的时候,要算,左侧延伸的长度 + 1(自身长度) + 右侧延伸的长度
        // 如果是-1,则可以延伸到最左或者最右
        // 如果不是-1,则只能延伸一部分
        int maxVolumn = 0;
        for (int i = 0; i < length; i++) {
            int leftSize = 0;
            int rightSize = 0;
            if (leftMin[i] == -1) {
                leftSize = i;  // 其实是i-0的简化
            } else {
                leftSize = i - leftMin[i] - 1;
            }

            if (rightMin[i] == -1) {
                rightSize = length - 1 - i;
            } else {
                rightSize = rightMin[i] - i - 1;
            }
            int size = leftSize + 1 + rightSize;
            int volumn = heights[i] * size;
            if (volumn > maxVolumn) {
                maxVolumn = volumn;
            }
        }
        return maxVolumn;
    }

    // 找到每一个节点右侧比其更小位置,反向,单调减栈
    private int[] getRightMinLocartion(int[] heights) {
        Stack<Integer> stack = new Stack<>();
        int[] rightMin = new int[heights.length];
        int location = heights.length - 1; //反向,从右往左走
        while (location >= 0) {
            if (stack.isEmpty()) {
                stack.push(location);
                rightMin[location] = -1;
                location--;
                continue;
            }
            if (heights[location] < heights[stack.peek()]) {
                stack.pop();
            } else if ((heights[location] == heights[stack.peek()])) {
                rightMin[location] = rightMin[stack.peek()];
                stack.push(location);
                location--;
            } else {
                rightMin[location] = stack.peek();
                stack.push(location);
                location--;
            }
        }
        return rightMin;
    }

    // 找到每个点左侧的比其更小的位置,正向,单调递减栈(栈顶到栈底递减,栈顶更大)
    private int[] getLeftMinLocartion(int[] heights) {
        Stack<Integer> stack = new Stack<>();
        int[] leftMin = new int[heights.length];
        int location = 0;
        while (location < heights.length) {
            if (stack.isEmpty()) {
                // 如果栈是空的,说明,该节点左侧没有比其更小的节点,因此
                leftMin[location] = -1;
                stack.push(location);
                location++;
                continue;
            }
            if (heights[stack.peek()] > heights[location]) {
                stack.pop();
            } else if (heights[stack.peek()] == heights[location]) {
                leftMin[location] = leftMin[stack.peek()];
                stack.push(location);
                location++;
            } else {
                leftMin[location] = stack.peek();
                stack.push(location);
                location++;
            }
        }
        return leftMin;
    }

}

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值