Leetcode 84: 柱状图中的最大矩形(扩散+单调栈)

题解

  • 题意:给定数组heights[],包含n个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的矩形的最大面积,矩形中间不能为空,样例,给定数组heights = [2,1,5,6,2,3], 输出10,即阴影部分的面积:
    在这里插入图片描述
  • 题解:首先考虑什么情况下可能围成最高的矩形?自然是要么够宽,要么够高,但是这两个并没有依赖性,因此需要共同考虑,而暴力做法在有两个自由变量的时候,复杂度都是 O ( n 2 ) O(n^2) O(n2),不合适,我们要考虑如何减少没必要的状态。
  • 这题中柱子的高度是一个非常重要的变量,因为无论多个柱子形成的形状是形还是形,围成矩形的高都是由中间最低的柱子高度决定的,当我们确定高度的时候,最大矩形的面积就只和宽度相关。
    • 我们可以考虑扩散思想:以当前柱子为最低高度,向左右扩散,直到遇到比当前高度低的柱子,就停止扩散,那么这样就可以得到以当前柱子高度为最小值,使用两个数组left[i]right[i]来记录当前柱子的最左下标和最右下标

    • 得到所有柱子所能扩散得到的最大面积之后,最后再进行一个循环遍历,就可以获得最大的矩形面积

    • 不幸的是,如果用i--i++来进行左边和右边的扩散,那么时间复杂度还是 O ( n 2 ) O(n^2) O(n2),什么情况下一些状态(柱子)是可以不用重复遍历的呢?

    • 减少状态: 其实对于每个柱子而言,我们只需要知道左边和右边第一个比当前柱子小的地方在哪里即可。

    • 单调栈记录左边最近较小值:单调(递增)栈的性质恰好满足这个特点,因为对于i个元素,单调栈中存放的是能够记录在第i个位置之前保留单调性的元素。换句话说,为了保留单调递增的特点,单调栈会抛弃不满足递增特性的元素,也就是当遍历到第i个元素的时候,单调栈会将比i高的元素都pop出去,这样一来,栈中与i相邻的柱子就是距离i最近的较低柱子。

    • 右边最近较小值:找到右边最近较小值没有任何难度,i++往后遍历的时候,自然就能找到,如果找不到,说明当前柱子就是全局最矮的柱子。

    • 特殊情况:对于全局最矮的柱子,我们会发现,左边会pop空,右边会超过n,那么为了防止越界的情况,我们需要给头部和尾部添加一个最小元素。

  • 实现:
class Solution {
    public int largestRectangleArea(int[] heights) {
        Stack<Integer> s1 = new Stack<Integer>();
        
        int[] new_heights = new int[heights.length + 1];
        for (int i = 1; i < heights.length + 1; i++) new_heights[i] = heights[i - 1];
        int n = new_heights.length;
        int max_size = 0;
        
        for(int i = 0;i < n;i++){
            while(!s1.empty() && new_heights[s1.peek()] > new_heights[i]){
                int curh = new_heights[s1.peek()];
                s1.pop();
                int l = s1.peek()+1;
                int r = i-1;
                max_size = Math.max(max_size, (r-l+1)*curh);
            }
            s1.push(i);
        }
        return max_size;
    }
}
  • 更直观的实现(类似单调栈的思路,但是用),来自Leetcode题解
class Solution {
    public int largestRectangleArea(int[] heights) {
        if (heights == null || heights.length == 0) return 0;
        int n = heights.length;
        int[] left_i = new int[n];
        int[] right_i = new int[n];
        left_i[0] = -1;
        right_i[n - 1] = n;
        int res = 0;
        for (int i = 1; i < n; i++) {
            int tmp = i - 1;
            while (tmp >= 0 && heights[tmp] >= heights[i]) tmp = left_i[tmp];
            left_i[i] = tmp;
        }
        for (int i = n - 2; i >= 0; i--) {
            int tmp = i + 1;
            while (tmp < n && heights[tmp] >= heights[i]) tmp = right_i[tmp];
            right_i[i] = tmp;
        }
        for (int i = 0; i < n; i++) res = Math.max(res, (right_i[i] - left_i[i] - 1) * heights[i]);
        return res;  
    }
}

题目

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值