https://leetcode.com/problems/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.
For example,
Given height = [2,1,5,6,2,3]
,
return 10
.
解题思路:
这是比较难的一道道题,但是O(n^2)的解法很容易写出。遍历每个histogram,对其往左右两个方向拓展,直到遇到数组边缘或者比它小的数字,这样便找出了此处的窗口宽度。于是此处的面积,就是窗口宽度乘以该histogram的高度。遍历结束之后,全局最大的面积也就出来了。
但是这个解法在数据量大的时候会TLE,这个结果是可以预见的。所以,本题一定不是要O(n^2)的解法,应该是O(n)。直觉上像是动态规划的题目,但是想了半天都没找到解法。
看到stack的tag,想到利用height的递增性,但是还是想不出解法。好吧,只能瞄了瞄大神的解法。
思路是,始终维护一个单调递增的stack。一旦当前height比栈顶小,就弹出栈顶,直到当前height进栈后,是递增的。那么,弹出栈顶的时候,如何计算当前面积?我们令当前准备弹出的位置为index,那么之前栈内,index往右一定都比index高,所以往右的都是宽度(rightIndex - hereIndex)。从index往左,是不是一定都比index矮?不一定,比如上图,1、5、6就是的。但是,1、5、6、2、3,2进来的时候,5、6被弹出了,所以只有1、2、3。计算2的宽度的时候,我们看到从1到2之间的5、6都是可以算在2的宽度里的。所以往左的宽度就是hereIndex - stack.peek()。这里的stack.peel()其实就是在栈内的index的前一个坐标。
我们看到,(rightIndex - hereIndex) + (hereIndex - stack.peek())其实就是rightIndex - stack.peek(),就是在栈内,index左右两侧坐标相减。但是,如果index是栈内的最后一个元素,弹出后,栈内为空。index是全局最短的元素,从左边的0一直到右边都是它的宽度,所以此时宽度是rightIndex + 1。
当然,整个遍历结束后,还要对栈内剩余的元素进行一遍同样的操作。
public class Solution { public int largestRectangleArea(int[] height) { int result = 0, current = 0; Stack<Integer> stack = new Stack<Integer>(); for(int i = 0; i < height.length; i++) { if(stack.empty() || height[stack.peek()] <= height[i]) { stack.push(i); } else { int rightIndex = stack.peek(); while(!stack.empty() && height[stack.peek()] > height[i]) { int hereIndex = stack.pop(); if(!stack.empty()) { // current = height[hereIndex] * ((rightIndex - hereIndex) + (hereIndex - stack.peek())); current = height[hereIndex] * (rightIndex - stack.peek()); } else { // current = height[hereIndex] * ((rightIndex - hereIndex + 1) + hereIndex); current = height[hereIndex] * (rightIndex + 1); } result = Math.max(result, current); } stack.push(i); } } if(!stack.empty()) { int rightIndex = stack.peek(); while(!stack.empty()) { int hereIndex = stack.pop(); if(!stack.empty()) { current = height[hereIndex] * (rightIndex - stack.peek()); } else { current = height[hereIndex] * (rightIndex + 1); } result = Math.max(result, current); } } return result; } }
这道题在思路上比较类似 Longest Valid Parentheses,都是维护了一个储存index的stack。对于栈内的任意一个index,栈顶元素是其窗口的右边界,而index左侧的元素则是窗口的左边界。面积的计算发生在栈顶元素弹出的时候。
因为每个元素都只被弹出一次,也仅仅计算一次,所以时间复杂度为O(n)。
这道题非常巧妙,也比较难。但是我们可以看出,stack这个数据结构并非像LIFO表面上这么简单。利用这个性质,加上单调性,本题可以得到解决。