题目:
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;
}
};