【LeetCode-数组/栈】柱状图中最大的矩形

题目描述

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

以上是柱状图的示例,其中每个柱子的宽度为 1,给定的高度为 [2,1,5,6,2,3]。

图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10 个单位。
示例:

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

题目链接: https://leetcode-cn.com/problems/largest-rectangle-in-histogram/

思路1

保留求解。使用两层循环,外层循环为 i,内层循环为 j,j>=i,计算 [i, j] 范围内的最大面积。代码如下:

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        if(heights.empty()) return 0;

        int maxArea = -1;
        for(int i=0; i<heights.size(); i++){
            int minHeight = INT_MAX;  // [i, j]范围内柱子的最低高度
            for(int j = i; j<heights.size(); j++){
                minHeight = min(heights[j], minHeight);
                maxArea = max(minHeight*(j-i+1), maxArea);
            }
        }
        return maxArea;
    }
};
// 超时

该方法超时未通过。

  • 时间复杂度:O(n^2)
  • 空间复杂度:O(1)

思路2

遍历数组,当遍历到 i 时,从 i 开始向两边寻找不小于 height[i] 的元素,然后求面积即可,记录最大的面积作为答案。代码如下:

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        if(heights.empty()) return 0;

        int maxArea = -1;
        for(int i=0; i<heights.size(); i++){
            int curHeight = heights[i];
            int left = i, right = i;
            while(left>=0 && heights[left]>=curHeight) left--;
            while(right<heights.size() && heights[right]>=curHeight) right++;
            maxArea = max(curHeight*(right-left-1), maxArea); // (right-1)-(left+1)+1=right-left-1
        }
        return maxArea;
    }
};
// 超时

虽然这种方法会超时,但是我们可以在这个思路的基础上进行优化,得到思路3.

  • 时间复杂度:O(n^2)
  • 空间复杂度:O(1)

思路3

思路 2 超时的原因是遍历到每个元素都要向两边扩散求矩形宽度,我们可以使用单调栈来加快求宽度的过程。

单调栈分为单调递增栈和单调递减栈。单调递增栈从栈底到栈顶的元素是递增的,单调递减栈则相反。这里我们使用的是单调递增栈。

单调递增栈插入元素的方法如下:

  • 如果新元素大于栈顶元素,则直接放进栈顶;
  • 如果新元素小于栈顶元素,则弹出栈顶元素,直到栈顶元素小于新元素,然后将新元素放进栈顶。

使用代码描述如下:

stack<int> st;
for(int i = 0; i < nums.size(); i++)
{
	while(!st.empty() && st.top() > nums[i])
	{
		st.pop();
	}
	st.push(nums[i]);
}

根据单调递增栈插入元素的特点,单调递增栈满足以下3个性质:

  • 栈中元素递增;
  • 当元素出栈后,新元素是从出栈元素位置向后数第一个小于出栈元素的元素:例如,数组为 [2,1,5,6,2,3],假设栈中的元素为 [1,5,6],新元素为 2,因为 2<6,则 6 要出栈,而 2 是从 6 往后数第一个小于 6 的元素;
  • 当元素出栈后,新的栈顶元素是出栈元素往左数第一个小于出栈元素的元素:与上面一样的例子,假设栈中的元素为 [1,5,6],新元素为 2,此时 6 出栈,则新的栈顶元素为 5,5 是 6 左边第一个小于 6 的元素。

通过后两个性质,我们就能快速地知道当前元素的左右边界,从而避免了思路 2 当中向左右遍历的方式。代码如下:

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        if(heights.empty()) return 0;

        int maxArea = -1;
        stack<int> s;
        heights.push_back(0);
        for(int i=0; i<heights.size(); i++){
            while(!s.empty() && heights[s.top()]>=heights[i]){
                int curHeight = heights[s.top()]; s.pop();
                if(s.empty()){
                    maxArea = max(i*curHeight, maxArea);
                }else{
                    maxArea = max((i-s.top()-1)*curHeight, maxArea);
                }
            }
            s.push(i);
        }
        return maxArea;
    }
};
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

参考

思路 3 主要参考了这篇题解这篇题解讲的也可以。

posted @ 2020-05-11 22:22  Flix  阅读(157)  评论(0编辑  收藏  举报