(leetcode)力扣100 73柱状图中最大的矩形(单调栈)

题目

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

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

数据范围

1 <= heights.length <=105

0 <= heights[i] <= 104

测试用例

示例1

java 复制代码
输入:heights = [2,1,5,6,2,3]
输出:10
解释:最大的矩形为图中红色区域,面积为 10

示例2

java 复制代码
输入: heights = [2,4]
输出: 4

题解1(博主题解,时空On,解法与官解1类似,细节不同而已)

java 复制代码
import java.util.*;

class Solution {
    public int largestRectangleArea(int[] heights) {
        int len = heights.length;
        if (len == 0) return 0;

        // left 数组记录的是:当前柱子【右边】第一个比它矮的柱子下标
        int[] left = new int[len];
        // right 数组记录的是:当前柱子【左边】第一个比它矮的柱子下标
        int[] right = new int[len];

        // 核心修正1:如果右边没有比自己矮的,右边界默认扩展到数组最右端 (len)
        Arrays.fill(left, len); 
        // 核心修正1:如果左边没有比自己矮的,左边界默认扩展到数组最左端 (-1)
        Arrays.fill(right, -1);

        Deque<Integer> deque = new LinkedList<>();
        
        // 1. 从左往右遍历,寻找每个柱子的【右边界】
        for(int i = 0; i < len; i++){
            // 核心修正2:用当前柱子的高度 heights[i] 与栈顶下标对应的高度 heights[deque.peek()] 比较
            while(!deque.isEmpty() && heights[i] < heights[deque.peek()]){
                // 栈顶元素遇到了比它矮的,它的右边界就确定了,是 i
                left[deque.poll()] = i;
            }
            deque.push(i);
        }

        deque.clear(); // 清空栈,准备下一次遍历

        // 2. 从右往左遍历,寻找每个柱子的【左边界】
        for(int i = len - 1; i >= 0; i--){
            while(!deque.isEmpty() && heights[i] < heights[deque.peek()]){
                // 栈顶元素遇到了比它矮的,它的左边界就确定了,是 i
                right[deque.poll()] = i;
            }
            deque.push(i);
        }
    
        int max = 0;
        
        // 3. 计算最大面积
        for(int i = 0; i < len; i++){
            // 核心修正3:宽度 = 右边界下标 - 左边界下标 - 1
            int width = left[i] - right[i] - 1;
            int temp = heights[i] * width;
            max = Math.max(temp, max);        
        }

        return max;
    }
}

题解2(官解1,时空同上)

java 复制代码
import java.util.*;

class Solution {
    public int largestRectangleArea(int[] heights) {
        int n = heights.length;
        // 这里的命名更符合常理:left 存左边界,right 存右边界
        int[] left = new int[n];
        int[] right = new int[n];
        
        Deque<Integer> mono_stack = new ArrayDeque<Integer>();
        
        // 1. 从左往右找【左边界】
        for (int i = 0; i < n; ++i) {
            // 把左边所有比当前柱子高(或相等)的都弹走
            while (!mono_stack.isEmpty() && heights[mono_stack.peek()] >= heights[i]) {
                mono_stack.pop();
            }
            // 弹干净后,如果栈空了,说明左边没有比我矮的,左边界是 -1;
            // 如果栈没空,栈顶剩下的那个就是左边第一个比我矮的柱子。
            left[i] = (mono_stack.isEmpty() ? -1 : mono_stack.peek());
            mono_stack.push(i); // 自己入栈
        }

        mono_stack.clear(); // 清空栈

        // 2. 从右往左找【右边界】
        for (int i = n - 1; i >= 0; --i) {
            // 把右边所有比当前柱子高(或相等)的都弹走
            while (!mono_stack.isEmpty() && heights[mono_stack.peek()] >= heights[i]) {
                mono_stack.pop();
            }
            // 弹干净后,栈空说明右边没矮的,右边界是 n;否则是栈顶元素。
            right[i] = (mono_stack.isEmpty() ? n : mono_stack.peek());
            mono_stack.push(i);
        }
        
        int ans = 0;
        // 3. 计算面积
        for (int i = 0; i < n; ++i) {
            // 宽度计算公式依旧是:右边界 - 左边界 - 1
            ans = Math.max(ans, (right[i] - left[i] - 1) * heights[i]);
        }
        return ans;
    }
}

题解3(关节2,时空同上,虽然两级没变,但时空都优于第一种解法)

java 复制代码
import java.util.*;

class Solution {
    public int largestRectangleArea(int[] heights) {
        int n = heights.length;
        int[] left = new int[n];
        int[] right = new int[n];
        
        // 由于右边界是在出栈时确定的,如果有些柱子(比如最矮的)一直不出栈,
        // 它的右边界就会缺失,所以必须提前用 n 兜底。
        // 左边界是在入栈时确定的,每个元素都会入栈,所以不需要给 left 兜底。
        Arrays.fill(right, n);
        
        Deque<Integer> mono_stack = new ArrayDeque<Integer>();
        
        // 仅仅通过一次从左到右的遍历搞定所有事
        for (int i = 0; i < n; ++i) {
            // 【确定右边界】:遇到比栈顶矮的柱子 i 了
            while (!mono_stack.isEmpty() && heights[mono_stack.peek()] >= heights[i]) {
                // 栈顶元素被弹出的瞬间,说明它的右边界找到了,就是当前挡路的 i
                right[mono_stack.peek()] = i;
                mono_stack.pop();
            }
            
            // 【确定左边界】:此时栈里比 i 高的已经被清理干净了
            // 栈顶剩下的元素,必然是 i 左边第一个比 i 矮的柱子!
            left[i] = (mono_stack.isEmpty() ? -1 : mono_stack.peek());
            
            // 记录完左右边界相关的逻辑后,i 安心入栈
            mono_stack.push(i);
        }
        
        int ans = 0;
        // 计算最大面积
        for (int i = 0; i < n; ++i) {
            ans = Math.max(ans, (right[i] - left[i] - 1) * heights[i]);
        }
        return ans;
    }
}

思路

这道题确实有一定的思维难度,但是想懂了以后很简单,以至于这道题因为博主从前做过,导致不到三十分钟就秒杀了,做了那么久对这个方法还是记忆犹新,甚至耗时不如前两个普通题。

我们看数据量,是10的五次方,我们通过普通的暴力根本不可能解决问题,这道题的题解1的思路就是预处理每一个柱状体左右两边,离他最近且小于他的柱子的下标,这样就可以算得这个柱子的高度所能带来的最大面积。

题解二是进阶优化版本,不需要遍历两次预处理,将过程优化为一次,具体解法我就用AI讲解的截图了,因为博主也是现学的,整体代码逻辑修改不大。

相关推荐
pp起床2 小时前
动态规划 | part03
算法·动态规划
mit6.8242 小时前
合法括号字符串|递归|树
算法
普通网友2 小时前
C++与Rust交互编程
开发语言·c++·算法
逆境不可逃2 小时前
【春节篇】LeetCode 热题 100 之 238.除了自身以外数组的乘积
数据结构·算法·leetcode
铸人2 小时前
再论自然数全加和 - 质数螺旋及其生成程序
数学·算法·数论·复数
散峰而望2 小时前
【算法竞赛】堆和 priority_queue
开发语言·数据结构·c++·算法·贪心算法·动态规划·推荐算法
WarPigs2 小时前
UI显示任务目的地标记的方法
算法·ui
蚊子码农3 小时前
算法题解记录-560和为k的子数组
算法
alexwang2113 小时前
B2007 A + B 问题 题解
c++·算法·题解·洛谷