leetcode_单调栈篇

单调栈篇

需要单调栈实现:依次遍历,需要找到单方向(从左开始,或从右开始)第一个比当前位置数大(递减栈)或数小(递增栈)的元素的情况。

LeetCode题目

739.每日温度(中等)

在这里插入图片描述
题目要求:

遍历每一个温度,寻找到第一个大于当天温度的天数,返回天数差距。这里可以看到满足需要单调栈的情况。

因为是找到第一个大于当天温度的温度,这里使用单调递减栈

class Solution {
    /**
     * 单调栈
     * 这里使用单调递减栈,因为要找到每个元素右侧第一个数大的元素
     * 栈为空或当前元素元素小于栈顶:入栈。
     * 当前元素元素大于栈顶:出栈直到栈顶元素小于当前元素或空栈,出栈的同时可以计算元素距离。之后再入栈。
     * @param temperatures
     * @return
     */
    public int[] dailyTemperatures(int[] temperatures) {
        // 单调递减栈
        Deque<Integer> stack = new LinkedList<>();
        int[] res = new int[temperatures.length];

        for(int i = 0; i < temperatures.length; i++) {
        	// 栈空或栈顶 > 当前元素,则入栈
            if(stack.isEmpty() || temperatures[stack.peek()] >= temperatures[i]) {
                stack.push(i);
            } else {
            	// 若栈顶 < 当前元素,则找到第一个大数,循环出栈计算直到栈顶 > 当前元素。
                while(!stack.isEmpty() && temperatures[stack.peek()] < temperatures[i]) {
                    int index = stack.poll();
                    res[index] = i - index;
                }
                // 出完栈后进栈
                stack.push(i);
            }
        }
        // 遍历之后栈内剩余元素为右侧无数大的元素,全都赋值0即可。
        while(!stack.isEmpty()) {
            int index = stack.poll();
            res[index] = 0;
        }

        return res;
    }
}

42.接雨水(困难)

在这里插入图片描述
在计算容器面积时,我们也是维护一个单调递减栈,在遇到一个栈顶 < 当前元素高度时,形成了一个矩形容器,可以进行计算面积。此时栈顶为低的高度,栈顶下一层的元素为矩形的左边高,当前元素高度为右边高,取两边的短边计算面积。

复杂容器情况,根据单调栈的性质是按照行(以矩形为单位,可能由多行组成) 进行计算的。
在这里插入图片描述

class Solution {
    /**
     * 单调栈
     * 运维一个单调递减栈,存储高度下标
     * 1.当栈顶下标对应高度 > 当前元素:直接入栈
     * 2.(当栈顶小标对应高度 == 当前元素:出栈,再入栈)这一步可以省略,第三步可以间接实现这一步。为什么相同高度只保留一个呢,这个是为了方便计算面积。
     * 3.当栈顶下标对应高度 < 当前元素:形成凹槽,计算面积,因为凹槽有可能为多行矩形叠加,这里按行计算;
     */
    public int trap(int[] height) {
        int len = height.length;
        if(len <= 2) {
            return 0;
        }
        // stack stores index of  array of height, initial stack push 0 the first index;
        // 单调递减栈
        Deque<Integer> stack = new LinkedList<>();
        stack.push(0);
        int res = 0;

        for(int i = 1; i < len; i++) {
            if(height[i] < height[stack.peek()]) {
                stack.push(i);
            } else if(height[i] == height[stack.peek()]) {
                stack.pop();
                stack.push(i);
            } else {
                int right = i;
                // stack.peek() is mid, when height[right] > height[mid], stack pop mid and then stack.peek is left;
                // 循环判断当前的right是否大于mid,若大于mid出栈,随后判断left是否存在(栈是否为空),如存在计算面积
                while(!stack.isEmpty() && height[right] > height[stack.peek()]) {
                    // mid is height of bottom;mid是底部高度
                    int mid = stack.pop();
                    // find left if left exit;
                    if(!stack.isEmpty()) {
                        // left是mid的栈下层元素,一定是 > mid的高度的,是潜在的面积计算高度
                        int left = stack.peek();
                        // 比较height[left],height[right]找到矩形短板高度,再减去底部高度height[mid],即为这一行的举行高度
                        int h = Math.min(height[left], height[right]) - height[mid];
                        int w = right - left - 1;
                        res += h * w;
                    }
                }
                stack.push(i);
            }
        }

        return res;
    }
}

239.滑动窗口最大值(困难)

在这里插入图片描述
题解

这里和之前的题目不同,因为滑动窗口涉及左出(元素过期),右出右进(单调的保持),所以不能用栈实现,要用双端队列。

每一次窗口移动,都要找到窗口内的最大值,所以需要一个单调递减双端队列存放元素下标。

遍历元素,当前队列为空或队列尾 > 当前元素,则入队。

若队列尾 < 当前元素,则不断出队直到满足上一行条件,再入队。

队头根据下标范围判定是否过期,若过期弹出队头。单调递减保持队头始终为最大值,若当前窗口内元素值满足窗口大小,保存队头对应值即可。

class Solution {
    /**
     * 双端队列
     * 使用双端队列存储元素下标,这是因为要通过下标判断队首是否过期,通过下标也能访问到数据;
     * 双端队列中下标的存放顺序,是按照下标对应元素的大小单调递减存放。若要加入的元素大于队尾对应元素,则移除队尾,直到满足单调递减;
     * 队列添加完元素后,要判断队首元素是否过期,这里就会用到下标。队首元素是否大于当前访问元素下标i - 窗口大小k,若不是则过期出队首。
     * 访问元素下标 >= 窗口时,窗口填满,可以按照队首存储当前窗口最大值至result数组中。
     * T(n) = O(n)
     * S(n) = O(k)
     * @param nums
     * @param k
     * @return
     */
    public int[] maxSlidingWindow(int[] nums, int k) {
        // 双端队列存储元素下标,
        Deque<Integer> queue = new LinkedList<>();
        int[] result = new int[nums.length - k + 1];

        for(int i = 0; i < nums.length; i++) {
            while(!queue.isEmpty() && nums[queue.peekLast()] < nums[i]) {
                queue.pollLast();
            }

            queue.addLast(i);

            // 这里while和if的效果一样,因为每一次窗口移动最多过期一位元素
            if(queue.peek() <= i - k) {
                queue.poll();
            }

            if(i + 1 >= k) {
                result[i + 1 - k] = nums[queue.peek()];
            }
        }
        
        return result;
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值