Problem
LeetCode 84. Largest Rectangle in Histogram
Difficulty : Hard
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 =10unit.
Example :
| 1 | Input: [2,1,5,6,2,3] | 
Analysis
这道题的题意是要求我们在一个给定的直方图中寻找最大的矩形。
Approach 1
首先, 从最简单最暴力的方法入手。
对于每一个元素,我们都可以找出一个以当前元素为高度的最大的矩形,那么,只需要遍历所有元素,就可以找出面积最大的矩形。
具体的做法如下:
对于数组中的每一个元素:
- 找出其左边第一个小于自身的元素的位置l
- 找出其右边第一个小于自身的元素的位置r
- 当前元素的高度h* (r-l- 1)就是以当前元素为高度的最大的矩形
然后找出所有元素中最大的矩形,就是本题的答案
代码如下:
| 1 | func largestRectangleArea(heights []int) int { | 
这种做法对于每一个元素最坏情况下都要遍历一次每一个元素,因此其时间复杂度为$O(n^2)$,在所有提交中只打败了12.5%的提交,耗时 960 ms,因此,这个算法有很大的提升空间
Approach2
我们可以使用一些技巧,将前面查询的左边界和右边界存储下来。
使用两个数组LeftLess和RightLess,用于存储当前位置上的左边界和右边界,用空间换取时间
具体做法如下:
- 为 - LeftLess的第一个元素赋值- -1,表示左边的尽头
- 为 - RightLess的最后一个元素赋值数组的长度,表示右边的尽头
- 从左边到右边遍历数组,寻找左边界: - 设left为左边元素的下标
- 如果left不是左边的尽头-1- left中的元素的数值大于自己的数值,那么将自己的左边界设为- left元素的左边界- left = LeftLess[left]
- 继续重复上面的步骤,直到左尽头或者遇到小于自己的数值
 
- 设当前元素的左边界为left
 
- 设
- 从右边到左边遍历数组,寻找右边界: - 设left为左边元素的下标
- 如果left不是左边的尽头-1- right中的元素的数值大于自己的数值,那么将自己的左边界设为- right元素的左边界- right = RightLess[right]
- 继续重复上面的步骤,直到左尽头或者遇到小于自己的数值
 
- 设当前元素的左边界为right
 
- 设
这种做法利用了之前元素的边界值来判断当前元素的边界值,比上面那种做法少了不少操作。
最后根据元素的高度和左右边界,就可以计算出以当前元素为高度的最大的矩形的面积
代码如下:
| 1 | func largestRectangleArea(heights []int) int { | 
由于基本上只需要很少的操作就可以判断出当前元素的边界值,因此其时间复杂度可以近似看作$O(n)$,这个提交在LeetCode上超过了100%的提交,只花了 12 ms,应该是go语言最优的做法。
Approach3
上面那种解法需要使用两个数组,但是其实使用一个堆栈也是可以解决的。
下面是使用堆栈的一种解决方式,其基本思想和上面有所不同,不过也是大同小异的。
代码如下:
| 1 | func largestRectangleArea(heights []int) int { | 
这种算法虽然空间复杂度比上面的做法低,但是耗费的时间却比上面的做法多一点。
Summary
第二种解法和第一种解法,其核心的思想基本上没有什么区别,但是耗费的时间上却差了几乎 100 倍。这就是优化算法的必要性。一些简单的技巧可以极大地优化我们的算法。


 
        