算法-单调栈

1. 每日温度(LeetCode 739)

给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,
其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。

输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]

思路

  • 单调栈的作用:用于解决寻找右边第一个比当前元素 大/小 的元素
  • 单调栈中存放的是数组的下标
  • 寻找右边第一个比当前元素大的元素时,从栈底到栈顶为递减序列。
  • 时间复杂度:O(n);空间复杂度:O(n)
class Solution {
    // 暴力解法
    public int[] dailyTemperatures(int[] temperatures) {
        int n = temperatures.length;
        int[] answer = new int[n];

        // 单调栈,存放的是下标
        Stack<Integer> stack = new Stack<Integer>();

        // 遍历数组元素
        for(int i = 0; i<n; ++i) {
            if(stack.empty()) {
                stack.push(i);
                continue;
            }
            while(!stack.empty() && temperatures[i] > temperatures[stack.peek()]) {
                answer[stack.peek()] = i-stack.peek();
                // 弹出栈顶下标
                stack.pop();
            } 
            // 下标i入栈
            stack.push(i);
        }

        // 最后留在战中的元素右边,answer默认为0
        return answer;
    }
}

2. 下一个更大元素 I(LeetCode 496)

nums1 中数字 x 的 下一个更大元素 是指 x 在 nums2 中对应位置 右侧 的 第一个 比 x 大的元素。

给你两个 没有重复元素 的数组 nums1 和 nums2 ,下标从 0 开始计数,其中nums1 是 nums2 的子集。

对于每个 0 <= i < nums1.length ,找出满足 nums1[i] == nums2[j] 的下标 j ,并且在 nums2 确定 nums2[j] 的 下一个更大元素 。如果不存在下一个更大元素,那么本次查询的答案是 -1 。

返回一个长度为 nums1.length 的数组 ans 作为答案,满足 ans[i] 是如上所述的 下一个更大元素 。

输入:nums1 = [4,1,2], nums2 = [1,3,4,2].
输出:[-1,3,-1]
解释:nums1 中每个值的下一个更大元素如下所述:
4 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。
1 ,用加粗斜体标识,nums2 = [1,3,4,2]。下一个更大元素是 3 。
2 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。

思路

  • 因为nums1和nums2有一种乱序的对应关系,并且没有重复元素。所以用map将值和下标建立对应关系,方便填充answer数组。
class Solution {
    public int[] nextGreaterElement(int[] nums1, int[] nums2) {
        int n1 = nums1.length;
        int n2 = nums2.length;
        // 存放结果
        int[] answer = new int[n1];
        Arrays.fill(answer, -1);

        // 用一个hashmap存放nums1[i]和i的映射关系
        HashMap<Integer, Integer> map = new HashMap<>();
        for(int i = 0; i<n1; ++i) {
            map.put(nums1[i], i);
        }

        // 单调栈,存放的是nums2的下标
        Stack<Integer> stack = new Stack<>();

        for(int i = 0; i<n2; ++i) {
            if(stack.empty()) {
                stack.push(i);
            }
            while(!stack.empty() && nums2[i] > nums2[stack.peek()]) {
                // 如果栈顶元素在nums1中出现过才需要记录
                if(map.containsKey(nums2[stack.peek()])) {
                    int index1 = map.get(nums2[stack.peek()]);
                    answer[index1] = nums2[i];
                }
                stack.pop();
            }
            stack.push(i);
        }

        return answer;
    }
}

3. 下一个更大元素II(LeetCode 503)

给定一个循环数组 nums ( nums[nums.length - 1] 的下一个元素是 nums[0] ),返回 nums 中每个元素的 下一个更大元素 。

数字 x 的 下一个更大的元素 是按数组遍历顺序,这个数字之后的第一个比它更大的数,
这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1 。

class Solution {
    public int[] nextGreaterElements(int[] nums) {
        int n = nums.length;
        int[] answer = new int[n];
        Arrays.fill(answer, -1);
        Stack<Integer> stack = new Stack<>();

        // 第一遍遍历
        for(int i = 0; i<n; ++i) {
            if(stack.empty()) 
                stack.push(i);
            while(!stack.empty() && nums[i] > nums[stack.peek()]) {
                answer[stack.peek()] = nums[i];
                stack.pop();
            }
            stack.push(i);
        }

        // 第二遍遍历,不需要完整遍历,遍历到最大元素重复即可
        for(int i = 0; i<n; ++i) {
            // 最后剩下来的是最大元素
            if(stack.size() == 1)
                break;
            while(!stack.empty() && nums[i] > nums[stack.peek()]) {
                answer[stack.peek()] = nums[i];
                stack.pop();
            }
        }

        return answer;
    }
}

4. 接雨水(LeetCode 42)(有难度)

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

class Solution {
    public int trap(int[] height) {
        int n = height.length;
        int sum = 0;
        Stack<Integer> stack = new Stack<>();

        stack.push(0);
        for(int i = 1;i<n; ++i) {
            if(height[i] < height[stack.peek()])
                stack.push(i);
            // 遇到相同数值的,需要更新下标
            else if(height[i] == height[stack.peek()]) {
                stack.pop();
                stack.push(i);
            }
            else {
                while(!stack.empty() && height[i] > height[stack.peek()]) {
                    int midIndex = stack.pop();
                    // 弹出midIndex之后,左边元素 + 右边栈外元素 才能形成凹槽
                    // 高 低 高 <=> height[stack.peek()], height[midIndex], height[i]
                    if(!stack.empty()) {
                        // 按行来计算雨水
                        int h = Math.min(height[stack.peek()], height[i]) - height[midIndex];
                        int w = i - stack.peek() - 1;
                        sum += h * w;
                    }
                }
                stack.push(i);
            }
        }

        return sum;
    }
}

5. 柱状图中最大的矩形(LeetCode 84)(有难度)

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。

class Solution {
    public int largestRectangleArea(int[] heights) {
        int n = heights.length;
        int[] newheights = new int[n+2];
        // 首部填0,确保栈始终非空
        newheights[0] = 0;
        // 尾部填0,确保以每个元素为高的矩形都被计算
        newheights[n+1] = 0;
        for(int i = 0; i<n; ++i)
            newheights[i+1] = heights[i];
        
        int result = 0;
        // 从栈底到栈顶递增
        Stack<Integer> stack = new Stack<>();
        stack.push(0);

        for(int i = 1; i<n+2; ++i) {
            if(newheights[i] >= newheights[stack.peek()])
                stack.push(i);
            else {
                // 矩形的左边界是 leftIndex + 1, 右边界是i-1
                while(newheights[i] < newheights[stack.peek()]) {
                    int h = newheights[stack.pop()];
                    int leftIndex = stack.peek();
                    int w = i - leftIndex - 1;
                    result = Math.max(result, h * w);
                }
                stack.push(i);
            }
        }

        return result;
    }
}
posted @ 2024-10-18 12:52  Frank23  阅读(3)  评论(0编辑  收藏  举报