leetcode 84. 柱状图中最大的矩形

问题描述

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


图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10 个单位。
示例:
输入: [2,1,5,6,2,3]
输出: 10

代码1(暴力)

先给一个超出时间限制的解法,时间复杂度为\(O(N^3)\):思路很简单,我们求以left为左边矩形,right为右边矩形的面积,实际只要找中间最矮的矩形即可(这决定了矩形的宽),长为right-left+1.

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        int n = heights.size();
        int maxarea = 0,minheight;
        for(int left = 0; left < n; left++)//left为最左边的矩形
        {
            maxarea = max(maxarea,heights[left]);//自己一个作为矩形
            for(int right = left + 1; right < n; right++)//right为最右边的矩形
            {
                minheight = heights[left];
                for(int k = left+1; k <= right; k++)
                {
                    minheight = min(minheight,heights[k]);//找到最矮的矩形
                }
                maxarea = max(maxarea,minheight*(right-left+1));
            }
        }
        return maxarea;
    }
};

代码2(暴力优化)

我们继续分析,因为我们要求\([left,right]\)之间最矮的矩形,假如我们已经知道了\([left,right-1]\)最矮矩形高度为minheight,那只需要比较\(\min(minheight,heights[right])\)即可,因此可以省去最内层循环,时间复杂度为\(O(N^2)\),但是依然超时:

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        int n = heights.size();
        int maxarea = 0,minheight;
        for(int left = 0; left < n; left++)//left为最左边的矩形
        {
            maxarea = max(maxarea,heights[left]);//自己一个作为矩形
            minheight = heights[left];
            for(int right = left + 1; right < n; right++)//right为最右边的矩形
            {
                minheight = min(minheight,heights[right]);
                maxarea = max(maxarea,minheight*(right-left+1));
            }
        }
        return maxarea;
    }
};

代码3(最终暴力优化)

这个版本时间复杂度也是\(O(N^2)\),只不过加了一个类似于剪枝的操作,假如\(heights[right]\geq heights[right-1]\),则\([left,right]\)最矮的矩形一定是\([left,right-1]\)中最矮的矩形,因此可以不用考虑\([left,right-1]\)的情况了。

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        int n = heights.size();
        int maxarea = 0,minheight;
        for(int  right= 0; right < n; right++)//right为最右边的矩形
        {
            if(right+1 < n && heights[right+1]>=heights[right])
                continue;
            minheight = heights[right];
            maxarea = max(maxarea,heights[right]);
            for(int left = right-1; left >= 0; --left)//left为最左边的矩形
            {
                minheight = min(minheight,heights[left]);
                maxarea = max(maxarea,minheight*(right-left+1));
            }
        }
        return maxarea;
    }
};

结果:

执行用时 :8 ms, 在所有 C++ 提交中击败了98.85%的用户
内存消耗 :15.8 MB, 在所有 C++ 提交中击败了6.95%的用户

从运行时间来看效果还不错,实际上代码1和2能够求解94/96个例子,添加的continue部分针对的就是那两个特例,一个全是1,一个是从0开始递增而已.

代码4(单调栈)

1.单调栈分为单调递增栈和单调递减栈

  • 1.1 单调递增栈即栈内元素保持单调递增的栈
  • 1.2 同理单调递减栈即栈内元素保持单调递减的栈

2.操作规则(下面都以单调递增栈为例)

  • 2.1 如果新的元素比栈顶元素大,就入栈
  • 2.2 如果新的元素较小,那就一直把栈内元素弹出来,直到栈顶比新元素小

3.加入这样一个规则之后,会有什么效果

  • 3.1 栈内的元素是递增的
  • 3.2 当一个栈内元素被弹出来时,说明新元素是第一个比该元素小的元素
  • 3.3 因为栈内元素是递增的,所以出栈的元素前面第一个比该元素小的就是新的栈顶

其实可以把这个想象成锯木板,如果木板都是递增的那我很开心,如果突然遇到一块木板i矮了一截,那我就先找之前最戳出来的一块(其实就是第i-1块),计算一下这个木板单独的面积,然后把它锯成次高的,这是因为我之后的计算都再也用不着这块木板本身的高度了。再然后如果发觉次高的仍然比现在这个i木板高,那我继续单独计算这个次高木板的面积(应该是第i-1和i-2块),再把它俩锯短。直到发觉不需要锯就比第i块矮了,那我继续开开心心往右找更高的。当然为了避免到了最后一直都是递增的,所以可以在最后加一块高度为0的木板。

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        int n = heights.size();
        int maxarea = 0;
        stack<int> st;
        heights.push_back(0);
        for(int i = 0; i < n+1; i++)
        {
            while(!st.empty() && heights[st.top()]>=heights[i])
            {
                int cur = st.top();
                st.pop();
                maxarea = max(maxarea,heights[cur]*(st.empty()?i:(i-st.top()-1)));
            }
            st.push(i);
        }
        return maxarea;
    }
};

注意代码中i-st.top()-1实际为(i-1)-(st.top()+1)+1,其中right = i-1,left = st.top()+1.
结果:

执行用时 :16 ms, 在所有 C++ 提交中击败了64.51%的用户
内存消耗 :16.6 MB, 在所有 C++ 提交中击败了6.95%的用户
posted @ 2020-02-29 09:10  曲径通霄  阅读(251)  评论(0编辑  收藏  举报