柱状图中最大的矩形---从暴力到单调栈优化

题目链接:柱状图中最大的矩形
题目描述:
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

求在该柱状图中,能够勾勒出来的矩形的最大面积。
在这里插入图片描述
以上是柱状图的示例,其中每个柱子的宽度为 1,给定的高度为 [2,1,5,6,2,3]。
在这里插入图片描述
图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10 个单位。

示例:

输入: [2,1,5,6,2,3]
输出: 10

题目分析:矩形的面积是由宽度和高度共同决定的,因此我们可以通过枚举这两个值来求最大值。假如就选择高度好了,对于每个H[i],我们从这个点开始向两边扩展,只要大于等于就能继续扩展,直到两边的边界值均小于H[i],此时的范围即i位置能达到的最大面积值
代码:

class Solution {
    public int largestRectangleArea(int[] heights) {
        int ans = 0;
        int n = heights.length;
        int l,r,leftMax = 0,rightMax = 0;
        for(int i = 0;i < n;i++){
            l = i;
            //向左扩展
            while(l>0&&heights[l-1]>=heights[i]) l--;
            leftMax = l;
            r = i;
            //向右扩展
            while(r<n-1&&heights[r+1]>=heights[i]) r++;
            rightMax = r;
            ans = Math.max(ans,(rightMax-leftMax+1)*heights[i]);        
        }
           return ans;
      }
}

时间复杂度为O(N^2)
优化:
单调栈是一种数据结构,维护的是递增/减的一组数据,一般情况下我们只需要在栈中存储数据的索引值即可。然后根据题目的需求选择递增还是递减。

对于每个i位置能达到的最大面积值,我们在向两边扩展的时候,比如向左边扩展,那么寻找的边界是左侧距离i最近且小于H[i]的位置,因此可以使用一个递增单调栈,h[i]比栈顶元素大的就进栈,直到遇到比栈顶元素小的h[i],此时我们就将栈顶元素出栈,直到栈顶元素小于H[i],此时栈顶元素一定是离i最近且小于H[i]的元素。简单来说就是,栈里面保存的是大于H[i]的,遇到H[i]了,此时我们就把那些大于H[i]的依次出栈,这样最后剩的肯定是大于H[i]的第一个元素,也就是我们的边界。右边同理,只不过寻找的边界是右侧距离i最近且小于H[i]的位置
需要特别处理的是h[0]与h[n-1],可以设置虚拟柱子,最左边为-1,最右边为n

注意栈维护的信息是索引值而不是元素值

class Solution {
    public int largestRectangleArea(int[] heights) {
        int n = heights.length;
        int[] left = new int[n];
        int[] right = new int[n];
        
        Stack<Integer> st= new Stack<Integer>();
        for (int i = 0; i < n; ++i) {
            while (!st.isEmpty() && heights[st.peek()] >= heights[i]) {
                st.pop();
            }
            left[i] = (st.isEmpty() ? -1 : st.peek());
            st.push(i);
        }

        st.clear();
        for (int i = n - 1; i >= 0; --i) {
            while (!st.isEmpty() && heights[st.peek()] >= heights[i]) {
                st.pop();
            }
            right[i] = (st.isEmpty() ? n : st.peek());
            st.push(i);
        }
        
        int ans = 0;
        for (int i = 0; i y< n; ++i) {
            ans = Math.max(ans, (right[i] - left[i] - 1) * heights[i]);
        }
        return ans;
    }
}

优化 :在求解left[i]的同时求解right[i] ,只需要在出栈的同时更新right即可。原因也很简单,当栈顶元素大于h[i]时,此时对于栈顶元素来说,h[i]是右侧第一个小于它的边界值。

class Solution {
    public int largestRectangleArea(int[] heights) {
        int ans = 0;
        int n = heights.length;
        Stack<Integer> st = new Stack<Integer>();
        int[] left  = new int[n];
        int[] right = new int[n];
        Arrays.fill(right, n);
        for(int i = 0;i < n;i++){
            while(!st.isEmpty()&&heights[st.peek()]>=heights[i]){
                    right[st.peek()] = i;
                    st.pop();
           }
            left[i] = st.isEmpty()? -1 : st.peek();
            st.push(i);
        }
        for(int i = 0;i < n;i++){
            ans = Math.max(ans,(right[i]-left[i]-1)*heights[i]);
        }
        return ans;
    }
}
 

由于每个元素只会入栈出栈仅一次,因此时间复杂度为O(n)
本质利用栈的先进后出的思想,再结合单调的性质解决问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值