leetcode 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.

leetcode Largest Rectangle in Histogram

Above is a histogram where width of each bar is 1, given height = [2,1,5,6,2,3].

leetcode Largest Rectangle in Histogram

The largest rectangle is shown in the shaded area, which has area = 10 unit.

 For example,

Given height = [2,1,5,6,2,3],
return 10.

直观一点的想法就是,以每个框为最高,左右两边找到当前框可以跨的长度。然后记录最大值即可。这是n方的。我敲都不敲了。因为肯定超时的。

那有没有好一点的呢。有的。

利用剪枝:虽然最坏也是n方,但是省了好多计算量,也能AC

可以通过选择合适的右边界,做一个剪枝(Pruning)。观察发现当height[k] >= height[k - 1]时,无论左边界是什么值,选择height[k]总会比选择height[k - 1]所形成的面积大。因此,在选择右边界的时候,首先找到一个height[k] < height[k - 1]的k,然后取k - 1作为右边界,穷举所有左边界,找最大面积。

java代码:

leetcode Largest Rectangle in Histogramleetcode Largest Rectangle in Histogram
  // O(n^2) with pruning
  public int largestRectangleArea1(int[] height) {
    // Start typing your Java solution below
    // DO NOT write main() function
    int area = 0;
    for (int i = 0; i < height.length; i++) {
      for (int k = i + 1; k < height.length; k++) {
        if (height[k] < height[k - 1]) {
          i = k - 1;
          break;
        } else {
          i = k;
        }
      }
      int lowest = height[i];
      for (int j = i; j >= 0; j--) {
        if (height[j] < lowest) {
          lowest = height[j];
        }
        int currArea = (i - j + 1) * lowest;
        if (currArea > area) {
          area = currArea;
        }
      }
    }
    return area;
  }
View Code

但显然还不是我们想要的。利用一个栈来记录吧,如下多好啊。

class Solution {
public:
    int Max(int a, int b){return a > b ? a : b;}
    int largestRectangleArea(vector<int> &height) {
        height.push_back(0); // 注意先加入了末尾0,那最后栈就会把我们想要的都弹出,并计算面积
        stack<int> stk;
        int i = 0;
        int maxArea = 0;
        while(i < height.size()){
            if(stk.empty() || height[stk.top()] <= height[i]){
                stk.push(i++);
            }else {
                int t = stk.top();
                stk.pop();
                maxArea = Max(maxArea, height[t] * (stk.empty() ? i : i - stk.top() - 1));
            }
        }
        return maxArea;
    }
};

看了网上的图,借用一下,他的图最后有误,在后面会说明。(图来自别人,部分红色部分字体是我加的)

就用题目中的[2,1,5,6,2,3]来解释一下这段代码吧。

首先,如果栈是空的,那么索引i入栈。那么第一个i=0就进去吧。注意栈内保存的是索引,不是高度。然后i++。

leetcode Largest Rectangle in Histogram

然后继续,当i=1的时候,发现h[i]小于了栈内的元素,于是出栈。(由此可以想到,哦,看来stack里面只存放height单调递增的索引

这时候stack为空,所以面积的计算是h[t] * i。t是刚刚弹出的stack顶元素。也就是蓝色部分的面积。

leetcode Largest Rectangle in Histogram

继续。这时候stack为空了,继续入栈。注意到只要是连续递增的序列,我们都要keep pushing,直到我们遇到了i=4,h[i]=2小于了栈顶的元素。

leetcode Largest Rectangle in Histogram

这时候开始计算矩形面积。首先弹出栈顶元素,t=3。即下图绿色部分。

leetcode Largest Rectangle in Histogram

接下来注意到栈顶的(索引指向的)元素还是大于当前i指向的元素,于是出栈,并继续计算面积,粉色部分。

leetcode Largest Rectangle in Histogram

最后,栈顶的(索引指向的)元素小于了当前i指向的元素,循环继续,入栈并推动i前进。直到我们再次遇到下降的元素,也就是我们最后人为添加的dummy元素0.

leetcode Largest Rectangle in Histogram

同理,我们计算栈内的面积。由于当前i是最小元素,所以所有的栈内元素都要被弹出并参与面积计算。

leetcode Largest Rectangle in Histogram

(这个图应该错了,黄色部分的框应该从下标2到5,深红色的框应该从下标零到5)

注意我们在计算面积的时候已经更新过了maxArea。

总结下,我们可以看到,stack中总是保持递增的元素的索引,然后当遇到较小的元素后,依次出栈并计算栈中bar能围成的面积,直到栈中元素小于当前元素。

可以这样理解这个算法,看下图。

leetcode Largest Rectangle in Histogram

例如我们遇到最后遇到一个递减的bar(红色)。高度位于红线上方的(也就是算法中栈里面大于最右bar的)元素,他们是不可能和最右边的较小高度bar围成一个比大于在弹栈过程中的矩形面积了(黄色面积),因为红色的bar对他们来说是一个短板,和红色bar能围成的最大面积也就是红色的高度乘以这些“上流社会”所跨越的索引范围。但是“上流社会”的高度个个都比红色bar大,他们完全只计算彼此之间围成的面积就远远大于和红色bar围成的任意面积了。所以红色bar是不可能参与“上流社会”的bar的围城的。因为虽然长度不占优势,但是团结的力量是无穷的。它还可以参与“比较远的”比它还要屌丝的bar的围城。他们的面积是有可能超过上流社会的面积的,因为距离啊!所以弹栈到比红色bar小就停止了。

(其实这个解释我不是很赞同,好像有点瑕疵,因为红色部分的是会在之后抛出的时候计算,所以先不用考虑)

另外一个细节需要注意的是,弹栈过程中面积的计算。

h[t] * (stack.empty() ? i : i - stack.top() - 1)

h[t]是刚刚弹出的栈顶端元素。此时的面积计算是h[t]和前面的“上流社会”能围成的最大面积。这时候要注意哦,栈内索引指向的元素都是比h[t]小的,如果h[t]是目前最小的,那么栈内就是空哦。而在目前栈顶元素和h[t]之间(不包括h[t]和栈顶元素),都是大于他们两者的。如下图所示:

leetcode Largest Rectangle in Histogram

那h[t]无疑就是Stack.top()和t之间那些上流社会的短板啦,而它们的跨越就是i - Stack.top() - 1。

所以说,这个弹栈的过程也是维持程序不变量的方法啊:栈内元素一定是要比当前i指向的元素小的。