LeetCode:84. Largest Rectangle in Histogram

题目:

Given n non-negative integers representing the histogram’s bar height
where the width of each bar is 1, find the area of largest rectangle
in the histogram.

在这里插入图片描述
Above is a histogram where width of each bar is 1, given height = [2,1,5,6,2,3].

在这里插入图片描述
The largest rectangle is shown in the shaded area, which has area = 10 unit.

题目大意就是给出一个柱状图的高度的数组,求出其中能画出的最大矩形的面积。

1.方法一
一个比较简单暴力,容易理解的方法:对于每一个方柱,分别向左向右遍历,到达第一个小于该方柱高度的方柱停止,此时画出了一个以该方柱为最小高度的最大矩形,可以计算出面积:

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        if (heights.empty())
            return 0;
        int max = 0;
        for (int i = 0; i < heights.size(); ++i) {
            if (heights[i] <= max/heights.size()) continue;//如果该方柱高度过低,那也没有计算的必要了,略过
            int temp = helper(heights, i);
            if (temp > max)
                max = temp;
        }
        return max;       
    }
private:
    int helper(const vector<int>& heights, int i) {
        if (heights[i] == 0) return 0;
        int l = i, r = i;
        while(l-1 >= 0 && heights[l-1] >= heights[i]) --l;
        while(r+1 < heights.size() && heights[r+1] >= heights[i]) ++r;
        return heights[i] * (r-l+1);
    }
};

2.方法二
解法来源:https://www.geeksforgeeks.org/largest-rectangle-under-histogram/
比较难以理解,但是该方法时间复杂度为O(N),值得思考。
方法是这样的:还是要找出以每个方柱为最低高度的最大矩形,关键就是找出左边第一个小于该高度的和右边第一个小于该高度的。维持一个stack,遍历输入的数组。如果当前遍历的该方柱高度大于等于栈顶的方柱高度,那么就将其压入栈;如果当前遍历的该方柱高度小于栈顶的方柱高度,就一直将栈顶元素弹出,直到该方柱高度大于等于栈顶的方柱高度。对于每个弹出的方柱,计算以它为最低高度的最大矩形的面积,计算的方法:弹出后当前栈顶的方柱是左边第一个小于该高度的方柱,当前遍历的方柱是右边第一个小于该高度的方柱。
这是从左往右遍历的。实际编程中,为了方便,往往在右边放一个高度为0的方柱作为哨兵,然后从右往左遍历。
光看文字描述比较难以理解,需要自己在草稿纸上举一个例子,将整个过程模拟一遍,好好体会一下。
下面是我的代码:

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        int sz = heights.size();
        if (sz == 0)
            return 0;
        
        stack<int> si;
        si.push(-1);
        int maxArea = 0;
        for (int i = 0; i < sz; ++i) {
            while (si.size() > 1 && heights[i] < heights[si.top()]) {
                int top = si.top();
                si.pop();
                maxArea = max(maxArea, heights[top] * (i - si.top() - 1));
            }
            si.push(i);
        }
        
        while (si.size() > 1) {
            int top = si.top();
            si.pop();
            maxArea = max(maxArea, heights[top] * (sz - si.top() - 1));
        }
        
        return maxArea;
    }
};

3.方法三
更新一下,最近重新做这道题,又发现了一个新的做法,虽然不能通过测试集中的最后一个测试,但是还是值得一看的。
方法是这样:找到最矮的那个方柱,计算以它为高的矩形面积;那么,答案要么是这个面积,要么是最矮方柱左半拉那片的最大矩形面积,要么是最矮方柱右半拉那片的最大矩形面积。简单的分治思想。

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        return helper(heights, 0, heights.size()-1);
    }
private:
    int helper(const vector<int> &heights, int left, int right) {
        if (left > right)
            return 0;
        int minHeight = INT_MAX, minIndex = -1;
        findMin(heights, left, right, minHeight, minIndex);
        int area = minHeight * (right - left + 1);
        return max(area, max(helper(heights, left, minIndex-1), helper(heights, minIndex+1, right)));
    }
    
    void findMin(const vector<int> &heights, int left, int right, int &minHeight, int &minIndex) {
        assert(left <= right);
        minHeight = heights[left];
        minIndex = left;
        for (int i = left + 1; i <= right; ++i)
            if (heights[i] < minHeight) {//如果有不止一个最矮的,取最左边一个
                minHeight = heights[i];
                minIndex = i;
            }
    }
};

但是缺点显而易见,因为是递归,容易有爆栈的风险。特别是,像是最后一个没能通过的例子那样,很多1,1,1,1。。。。后果就是有多少1就会递归多少次,复杂度为O(n);理想情况是:如果有不止一个最矮的,那么取到中间的最好,那么复杂度可以降为O(lgn),但是目前没有一个好的办法这么做(总不能遍历两遍吧)。
但是,这种做法总归是有用的,值得思考。

4.方法四
分治法:https://leetcode.com/problems/largest-rectangle-in-histogram/discuss/28910/Simple-Divide-and-Conquer-AC-solution-without-Segment-Tree
最大面积要么是左半边里面,要么是右半边里面,要么是包含中间两个方柱向左右延伸出去这么一种情况。
后一种情况向两边延伸的复杂度是O(n),也就是复杂度为:T(n) = 2T(n/2) + O(n).所以总的时间复杂度为O(nlgn),空间复杂度为O(1)(算上栈的使用的话为O(lgn))

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        if (heights.empty())
            return 0;
        return helper(heights, 0, heights.size()-1);
    }
private:
    int helper(const vector<int>& heights, int left, int right) {
        if (left > right)
            return 0;
        if (left == right)
            return heights[left];
        int mid = left + (right - left) / 2;
        return max(max(helper(heights, left, mid), helper(heights, mid+1, right)), maxCombineArea(heights, left, mid, right));
    }
    
    int maxCombineArea(const vector<int>& heights, int left, int mid, int right) {
        int i = mid, j = mid + 1;
        int area = 0, h = min(heights[i], heights[j]);
        while (i >= left && j <= right) {
            h = min(h, min(heights[i], heights[j]));
            area = max(area, h * (j-i+1));
            if (i == left)
                j++;
            else if (j == right)
                i--;
            else {
                if (heights[i-1] > heights[j+1])
                    --i;
                else
                    ++j;
            }
        }
        return area;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值