数据结构算法刷题--单调栈

1. 每日温度

  • 题目:https://leetcode.cn/problems/daily-temperatures/description/
  • 思路:单调栈,栈顶到栈底单调递增,栈内存放温度的索引;每加入一个新的元素,如过栈顶元素对应的温度小于当前温度,那么栈顶元素右边第一个比它高的温度就找到了。
  • 代码实现:
// 单调栈
// 对于每个元素要找它右边第一个比他大的元素,那么对于每一个已经入栈的元素,当后面的元素加入时
// 如果比他大,那么就将栈顶元素出栈,同时计算出栈元素右边第一个比他大的元素的距离
// 如果小于等于,直接入栈。栈内一直保持栈顶到栈底单调递增

class Solution {
    public int[] dailyTemperatures(int[] temperatures) {
        int length = temperatures.length;
        int[] res = new int[length];

        // 栈 -- 里面放数组元素的索引,值可以直接通过数组取,索引方便计算距离
        Deque<Integer> stack = new LinkedList<>();
        stack.add(0);

        // 遍历,维持单调栈
        for(int i = 1; i < length; ++i) {
            // 判断栈顶与当前元素的关系
            if(temperatures[stack.peek()] < temperatures[i]) {
                // 栈顶小于当前元素,那么栈顶元素右边第一个比他大的出现了
                // 栈顶弹出加入新元素以维持单调栈,并计算距离
                // 同时新的栈顶也要进行判断
                while(!stack.isEmpty() && temperatures[stack.peek()] < temperatures[i]) {
                    // 计算距离
                    res[stack.peek()] = i - stack.peek();
                    // 栈顶弹出
                    stack.pop();
                }
                // 当前元素加入
                stack.push(i);
            }
            else {
                // 栈顶元素 >= 当前元素,直接入栈
                stack.push(i);
            }
        }

        // 最后单调栈内留下的元素都没有右边比它大的,在数组初始化时已经初始化为0
        return res;
    }
}

2. 下一个更大元素I

  • 题目:https://leetcode.cn/problems/next-greater-element-i/submissions/
  • 思路:单调栈,相比每日温度只是多了一个要找 nums1 中的元素在 nums2 中 – 依然是对 nums2 进行单调栈相关遍历,只是在出现右边第一个比当前数字大的时,要判断栈顶元素是否在 nums1 出现 – 用哈希判断要弹出的栈顶元素是否在 nums1 中出现过。没有右边比他大的数字结果是 -1,可以先将 res 数组填充为 -1。
  • 代码实现:
// 单调栈,相比每日温度只是多了一个要找 nums1 中的元素在 nums2中
// 那么依然时对 nums2 进行单调栈相关遍历,只是在出现右边第一个比当前数字大的时,要判断栈顶元素是否在 nums1 出现
// 判断要弹出的栈顶元素是否在 nums1 出现 -- 哈希

class Solution {
    public int[] nextGreaterElement(int[] nums1, int[] nums2) {
        // 存放结果的数组
        int len = nums1.length;
        int[] res = new int[len];
        // 给 res 全填充为 -1
        Arrays.fill(res, -1);

        // 哈希映射 -- 拿值找索引
        Map<Integer, Integer> map = new HashMap<>();
        for(int i = 0; i < len; ++i) {
            map.put(nums1[i], i);
        }

        // 栈,里面直接装数就行了,对应索引在哈希中
        Stack<Integer> stack = new Stack<>();
        stack.push(nums2[0]);

        // 遍历并维护单调栈 -- 栈顶到栈底单调递增
        for(int i = 1; i < nums2.length; ++i) {
            if(nums2[i] > stack.peek()) {
                // 进来了更大的元素 -- while 循环重复进行栈顶的弹出
                while(!stack.isEmpty() && nums2[i] > stack.peek()) {
                    // 看一下栈顶元素是否在哈希中存在,不存在就不需要更新结果数组
                    if(map.containsKey(stack.peek())) {
                        // 存在 -- 取出索引,然后更新 res
                        res[map.get(stack.peek())] = nums2[i];
                    }
                    // 栈顶出栈
                    stack.pop();
                }
                stack.push(nums2[i]);
            }
            else{
                stack.push(nums2[i]);
            }
        }

        return res;
    }
}

3. 下一个更大元素 II

  • 题目:https://leetcode.cn/problems/next-greater-element-ii/submissions/
  • 思路:单调栈,特别之处在于是一个循环数组 – 将数组“拼接”两个到一起就可以了。只用在遍历的时候遍历 nums 长度的2倍,然后元素的选取可以通过 i % nums.length,从而实现模拟“拼接”。
  • 代码实现:
// 单调栈,特别之处在于是一个循环数组 -- 将数组“拼接”两个到一起就可以了
// 只用在遍历的时候遍历 nums 长度的2倍,然后元素的选取可以通过 i % nums.length,从而实现模拟“拼接”

class Solution {
    public int[] nextGreaterElements(int[] nums) {
        int size = nums.length;

        // 结果数组
        int[] res = new int[size];
        Arrays.fill(res, -1);

        // 栈 -- 存放索引,因为 res 中需要索引来放置结果
        Stack<Integer> stack = new Stack<>();
        stack.add(0);

        // 遍历,同时维护单调栈 -- 遍历 2 倍 nums.length,模拟拼接表示循环数组
        for(int i = 1; i < size * 2; ++i) {
            // nums、res中的索引遍历时都用 i % size
            if(nums[stack.peek()] < nums[i % size]) {
                while(!stack.isEmpty() && nums[stack.peek()] < nums[i % size]) {
                    // 栈顶元素的结果得到了
                    res[stack.peek()] = nums[i % size];
                    stack.pop();
                }
                stack.push(i % size);
            }
            else {
                stack.push(i % size);
            }
        }

        return res;
    }
}

4. 接雨水

  • 题目:https://leetcode.cn/problems/trapping-rain-water/submissions/
  • 思路:单调栈:按照行来计算接到的雨水体积。
    在这里插入图片描述
    • 栈内按柱子的高低,从栈顶到栈底升序排列柱子对应的索引
      在这里插入图片描述

    • 1、当新遍历到的柱子高于栈顶的柱子,那么对于栈顶元素来说,他现在是一个凹槽(要注意左边可能没有柱子了)。出现凹槽就计算按照“行”能接到雨水的体积 – 当前这里形成的行凹槽,当前栈顶就作为这一行的底部高度bottom,左边能到栈顶元素弹出后新的栈顶,右边能到当前遍历到的位置,那么二者坐标差就是宽度,高度直接就是左右两边高度的小者和底部高度的高度差;

    • 2、当前遍历到的柱子与栈顶相等,弹出栈顶,因为相等的两个柱子之间不可能存放雨水

    • 3、当前遍历到的柱子小于栈顶元素,入栈

  • 代码实现:
// 单调栈:按照行来计算接到的雨水体积
// 栈内按柱子的高低,从栈顶到栈底升序排列柱子对应的索引
// 1、当新遍历到的柱子高于栈顶的柱子,那么对于栈顶元素来说,他现在是一个凹槽(要注意左边可能没有柱子了)
// 出现凹槽就计算按照“行”能接到雨水的体积
// 那么当前这里形成的行凹槽,左边能到栈顶元素弹出后新的栈顶,右边能到当前遍历到的位置,那么二者坐标差就是宽度
// 高度直接就是左右两边高度的小者和栈顶柱子的高度差
// 2、当前遍历到的柱子与栈顶相等,弹出栈顶,因为相等的两个柱子之间不可能存放雨水
// 3、当前遍历到的柱子小于栈顶元素,入栈

class Solution {
    public int trap(int[] height) {
        // 1、记录雨水总量
        int res = 0;

        // 2、栈,存放柱子的索引,高度通过数组拿索引取出
        Stack<Integer> stack = new Stack<>();
        stack.push(0);

        // 3、遍历,同时保持栈按照柱子高度从栈顶到栈底升序
        for(int i = 1; i < height.length; ++i) {
            if(height[i] > height[stack.peek()]) {
                // 3.1 情况1,形成了凹槽
                while(!stack.isEmpty() && height[i] > height[stack.peek()]) {
                    // 栈顶弹出作为底
                    int bottom = stack.pop();
                    // 确保左边是还有柱子从而能形成接水控件
                    if(!stack.isEmpty()) {
                        // 能接水,计算
                        // 高度 -- 左(当前新的栈顶)、右(当前遍历到的)中的小者与底的差
                        int heightDiff = Math.min(height[stack.peek()], height[i]) - height[bottom];
                        // 宽度,左右位置差
                        int widthDiff = i - stack.peek() - 1;
                        // 计算这一“行”接到水的体积
                        res += heightDiff * widthDiff;
                    }
                }
                // 入栈
                stack.push(i);
            }
            else if(height[i] == height[stack.peek()]) {
                // 3.2 情况2,两者相等,弹出栈顶,放入新的;因为两个相等的之间不可能接水
                stack.pop();
                stack.push(i);
            }else {
                // 3.3 情况3,当前小于栈顶,直接入栈,等着可能形成凹槽
                stack.push(i);
            }
        }

        return res;
    }
}

5. 柱状图中最大的矩形

  • 题目:https://leetcode.cn/problems/largest-rectangle-in-histogram/submissions/
  • 思路:单调栈:对于每个柱子,以他的高度能形成的最大矩形是左右到小于他的高度为宽,他自己的高度为高。
    • 那么对于每个在栈顶的元素,它能够计算时应当是它右边的比它低的柱子被遍历然后要进来了,此时就应当弹出栈顶来计算它所能形成的最大矩形,然后此时借助单调栈的特性,新的栈顶就应该是左边比它低的索引。从而得到单调栈从栈顶到栈底应当单调递减。
    • 注意此时整个数组单调递增,那么最后结束的位置也不会触发计算矩形面积的条件,所以要在最后补一个0;同理,如果整个数组单调递减,计算第一个元素形成的面积时左边没有则计算的是0,所以要在数组开头补一个0
  • 代码实现:
// 单调栈:对于每个柱子,以他的高度能形成的最大矩形是左右到小于他的高度为宽,他自己的高度为高
// 那么对于每个在栈顶的元素,它能够计算时应当是它右边的比它低的柱子被遍历然后要进来了
// 此时就应当弹出栈顶来计算它所能形成的最大矩形,然后此时借助单调栈的特性,新的栈顶就应该是左边比它低的索引
// 从而得到单调栈从栈顶到栈底应当单调递减
// 注意此时整个数组单调递增,那么最后结束的位置也不会触发计算矩形面积的条件,所以要在最后补一个0
// 同理,如果整个数组单调递减,计算第一个元素形成的面积时左边没有则计算的是0,所以要在数组开头补一个0

class Solution {
    public int largestRectangleArea(int[] heights) {
        // 1、数组开头结尾补0
        int[] newHeights = new int[heights.length + 2];
        newHeights[0] = 0;
        newHeights[newHeights.length - 1] = 0;
        // 拷贝heights过来
        for(int i = 0; i < heights.length; ++i) {
            newHeights[i + 1] = heights[i];
        }

        // 2、存放结果的变量
        int res = 0;

        // 3、栈,栈顶到栈底单调递减,存放索引
        Stack<Integer> stack = new Stack<>();
        stack.push(0);

        // 4、遍历
        for(int i = 1; i < newHeights.length; ++i) {
            if(newHeights[stack.peek()] > newHeights[i]) {
                // 4.1 情况1,对于栈顶元素,找到了右边比它低的
                while(newHeights[stack.peek()] > newHeights[i]) {
                    int height = newHeights[stack.pop()];
                    int width = i - stack.peek() - 1;
                    res = Math.max(res, height * width);
                }
                stack.push(i);
            }
            else if(newHeights[stack.peek()] == newHeights[i]) {
                // 4.2 情况2,相等,可以弹出用新的位置来计算,都是这个高度能形成的最高的
                stack.pop();
                stack.push(i);
            }
            else {
                // 4.3 情况3,当前元素高于栈顶,直接入栈
                stack.push(i);
            }
        }

        // 5、返回结果
        return res;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值