本文会总结以下两道题目:
11 - Container With Most Water - 直方图中盛水问题 (Medium)
84 - Largest Rectangle in Histogram - 直方图中最大矩形面积问题 (Hard)
还有两道题目也是直方图中面积问题,在另一篇中总结,(还没写- -)
42 - Trapping Rain Water - 直方图中接雨水问题 (Hard)
407 - Trapping Rain Water II - 42题的二维变三维 (Hard)
11. Container With Most Water
Given n non-negative integers a1, a2, ..., an , where each represents a point at coordinate (i, ai). n vertical lines are drawn such that the two endpoints of line i is at (i, ai) and (i, 0). Find two lines, which together with x-axis forms a container, such that the container contains the most water.
Note: You may not slant the container and n is at least 2.
The above vertical lines are represented by array [1,8,6,2,5,4,8,3,7].
In this case, the max area of water (blue section) the container can contain is 49.
解:
这道题的意思就是用直方图中任意两个柱子当边界,中间部分盛水,求最大的能盛水的多少,也就是最大的形成的矩形的面积。很明显,由于短板效应,能盛水多少取决于两个板子中间短的那个,而且中间部分是完全不用考虑的,两个任意板子 h[i] 和 h[j] (0 <= i < j <= n)形成的面积就是 min(h[i], h[j]) * (j - i)。
出于以上分析,这道题就很适合用夹逼的方式计算,也就是两个哨兵分别从最左和最右向内收,直到两个哨兵相遇位置结束。我们需要设计的就是两个哨兵想内遍历时候的判断方式,也就是到底谁动。代码结构如下:
int maxArea(vector<int>& height)
{
int res;
int l = 0, r = height.size() - 1;
while(l < r)
{
// 计算res
}
return res;
}
由于上面提到的短板效应,两个板子中间短的决定矩形面积,长的板子是没有实质性作用的。如果我们将长的板子的哨兵向内移动,短的不动的话,无论如何矩形面积都不可能变大的,就算板子变长了也不会影响盛水高度,反而由于板子之间距离减少了一而使得面积变小。所以在哨兵移动的时候,应该是短的向内移动,长的板子不动,只有这样才有可能让盛水高度变高,这样才有可能在板子间距离变少的情况下,盛水总量变大。两个板子一样高的情况下,两个哨兵哪个向内移动都可以。分析后很容易写出代码:
int maxArea(vector<int>& height)
{
int l = 0, r = height.size() - 1, max_water = -1;
while(l < r)
{
max_water = max(max_water, (r - l) * min(height[l], height[r]));
if(height[l] < height[r]) ++l;
else --r;
}
return max_water;
}
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.
Example:
Input: [2,1,5,6,2,3] Output: 10
解:
这道题和上边的盛水一样是求最大矩形面积,不过约束条件从两端的板子高度变成了板子内部,也就是在这个直方图所有板子组成的图形中画矩形,面积最大的是多少。
由于约束条件是最短的板子,所以用夹逼的方式是不行的(中间的板子可能比两端的板子更低)。应该是从每个板子出发,看看以这个板子为高度最大的矩形面积是多少,比如上图中第一个高度为2的板子,高为2,宽就只有1,因为后边的1比它低。也就是说,对于每个板子,从它所在位置分别向左向右走,走到比这个它低的板子停止,这些和它连着的高度 >= 它的板子组合起来就是当前这个板子为高度组成的最大矩形。简单地说就是,对于每个板子,矩形的宽就是板子的高,我们要计算的就是矩形的长。AC代码如下:
int largestRectangleArea(vector<int>& heights)
{
int res = 0; // 如果heights是空的,直接返回0
int len = heights.size();
for(int i = 0; i < len; i++)
{
if(i > 0 && heights[i] == heights[i - 1])
continue;
int l = i - 1, r = i + 1;
while(l >= 0 && heights[l] >= heights[i])
--l;
while(r <= len - 1 && heights[r] >= heights[i])
++r;
res = max(res, (r - l - 1) * heights[i]);
}
return res;
}
算法思路的核心就是:限制一个板子能组成的矩形面积的是,它左边第一个比它低的板子,和它右边第一个比它低的板子。上边的代码虽然AC,但是效率较低,主要是因为每个板子相当于遍历整个直方图,时间复杂度为 。对于每个板子,我们其实不需要遍历它左右的所有板子,如果我们存起来它前边的板子,我们就知道它左边能到哪里了。
我们可以利用一个stack,比如叫 bar_stack,从左往右遍历直方图的每个板子 bar[i],如果 bar_stack.top() < bar[i] ,那就说明这个板子比 stack里的都高,push进stack,如果 bar_stack.top() = bar[i],那就说明有连续的相同高度的板子,但是不能和上边的代码一样直接跳过,不过进行 if 和 else 那种处理都行。重点是,如果 bar_stack.top() > bar[i],那就是说,我们找到了 bar_stack.top() 这个板子右边第一个比它低的板子!!而stack中它前一个板子一定它左边第一个比它低的板子!!我们就很容易计算这个板子组成的矩形了,也就是(右边板子的索引 - 左边板子的索引)* bar_stack.top() 这个板子的高度。
理解了上面的分析(更加详细的英文分析在 https://www.geeksforgeeks.org/largest-rectangle-under-histogram/),就能明白 stack 里push的不是板子高度,而是板子索引,这样就很容易写出代码。有一个小细节就是,因为 bar[i] 可能会比stack中不止一个bar高度要低,所以需要pop很多次,而且对于小的 bar[i] ,i 还是要放进去的,所以用 while 会比 for 更好写。(英文讲解文档中相同高度的板子也被放入stack中,也就是push之后,再pop,不过 hist[s.top()] <= hist[i] 这个判断改为 < 也是对的,也就是pop出去,再push)。比如 3, 9, 9, 9 这个例子,每个9用这个算法算出来的是不一样的结果,不过最终算出来最大的是对的。感觉这道题这么做还是挺复杂的,还不如用我第一种方法。。。。。但是这种方法快,省去了从每块板子向左向右的遍历。
int largestRectangleArea(vector<int>& heights)
{
int res = 0, i = 0;
stack<int> hs;
while (i < heights.size())
{
if (hs.empty() || heights[i] >= heights[hs.top()]) // 比它高,或相同就push进去
hs.push(i++);
else if (heights[i] < heights[hs.top()]) // 重点部分
{
int t = hs.top();
hs.pop();
res = max(res, heights[t] * (hs.empty() ? i : i - hs.top() - 1)); // i 表示的是 i - 0
}
}
// 现在 i 是 heights.size()
// 所有height遍历完成,还要将hs遍历一遍,如果12345,那现在stack里就是12345,res还没有计算过
while (!hs.empty())
{
int t = hs.top();
hs.pop();
res = max(res, heights[t] * (hs.empty() ? i : i - hs.top() - 1)); // i 表示的是 i - 0
}
return res;
}