剑指 Offer II 039. 直方图最大矩形面积(单调栈)<必看>


题目

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

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

示例 1:

在这里插入图片描述
输入:heights = [2,1,5,6,2,3]
输出:10
解释:最大的矩形为图中红色区域,面积为 10

示例2:
在这里插入图片描述
输入: heights = [2,4]
输出: 4

提示:

1 <= heights.length <=105
0 <= heights[i] <= 104

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/0ynMMM


一、解题思路

思路:
暴力枚举的一种思路是枚举某一根柱子,将其高度固定为矩形的高度h,随后两边延伸,寻找高度比h小的柱子,也就是矩形的边界,当左右两边界宽度为w,那么对应的矩形面积为 w * h。
枚举过程中选取最大的面积,答案也就出来了。其时间复杂度为O(n2)会超出时间限制。
所以根据此思路,做点优化。

单调栈
边界:
在讲单调栈前,我们需要明确知道在向左延伸时,什么情况下,哪根柱子会有可能成为左边界
有3个下标: i1 < i2 < i3,找 i3的左边界

假设heights[ i1] >= heights[ i2] :
①当 heights[ i3] > heights[ i2] ,那么 i3 的左边界就是 i2;
②当 heights[ i3] <= heights[ i2] ,因为要找高度比 i3小的,所以 i1,i2都不是

假设heights[ i1] < heights[ i2] :
① 当 heights[ i3] > heights[ i2] ,那么 i3的左边界就是 i2
② 当 heights[ i3] = heights[ i2],那么 i3的左边界就是 i1
③ 当 heights[ i3] < heights[ i2],heights[ i3] > heights[ i1],那么i3的左边界就是i1
④ 当 heights[ i3] < heights[ i2],heights[ i3] < heights[ i1],i1,i2都不是

从上面的结论就可以知道当 heights[ i1] >= heights[ i2] 且 i1<i2,那么无论 i3的高度如何,i1怎么也不可能成为左边界。只有 i1<i2这种递增的柱子才有可能成为边界

所以我们可以根据栈先进后出的特性,遍历存储有可能成为边界的柱子的下标,形成一个自底向上递增的单调栈:

因为每根柱子都有进栈机会,所以不设入栈条件
出栈条件:
栈不为空且 栈顶对应的柱子 高度大于 当前柱子 ,栈顶元素出栈,重复此过程,直到栈顶对应的高度小于 当前柱子(此栈顶对应的柱子就是左边界)或栈空。我们可以假设一个虚拟柱子的下标为-1,其高度无限低,栈空时,也就是虚拟柱子当左边界。

照相同思路,从右往左遍历,也可确定右边界。确定了左右边界也就能知道最后的面积了。

优化:
我们也可以从出栈时明确右边界,
当我们要找当前栈顶对应的柱子的右边界,我们要怎么找呢?
当 当前栈顶元素被弹出时,也就是遇到一个柱子其高度比栈顶对应柱子低或相同。。。
也就是当栈顶元素被弹出时,必定有一个高度比它低或相同的柱子进来了,那么这根柱子就是栈顶柱子的右边界

有人会感觉有点不对劲了,如果两者高度相同的话,那么对栈顶对应的柱子来说,右边界是不是不正确。
答案是,确实算的不正确,但是对最终的结果没有影响的,因为高度相同嘛,虽说对于栈顶对应的柱子来说右边界是少了,但是对于当前要进来的这根柱子,是可以算出正确的右边界的。(高度相同,所以当前进来的这根柱子会把正确右边界求出)

在遍历结束后,如果栈中还有元素,那么其右边界位置即为最右边,也就是heights数字的长度n.

二、代码

class Solution {
    public int largestRectangleArea(int[] heights) {
        int n = heights.length;
        int[] left = new int[n];
        int[] right = new int[n];
        //为右边界定值n,如果最后剩下的数没机会pop,那么右边界就是n
        //例如示例1 中的1,2,3都没机会pop,那么其右边界就是n
        Arrays.fill(right,n);

        Deque<Integer> eq = new LinkedList<>();
        for(int i=0;i<n;i++){
            //heights数组中每个数都有机会入栈,所以代码思路不写条件入栈,因为都会入
            //写出栈条件
            //持续出栈,直到heights[eq.peek()]<heights[i]
            while(!eq.isEmpty()&&heights[eq.peek()]>=heights[i]){
                right[eq.pop()]=i;
            }
            //先不入栈,查看栈顶,把左边界放进去
            left[i]= eq.isEmpty() ? -1:eq.peek();
            eq.push(i);
        }

        int res = 0;
        for(int i=0;i<n;i++){
        	//宽乘高
            res=Math.max(res,(right[i]-left[i]-1)*heights[i]);
        }
        return res;
    }
}

参考力扣解析:
https://leetcode-cn.com/problems/0ynMMM/solution/zhi-fang-tu-zui-da-ju-xing-mian-ji-by-le-pcyu/

侵删

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值