LeetCode传送门
求柱状图(Histogram)中的最大矩形(Largest Rectangle),是一道ACM比赛题,又是一道Leetcode题,还是一道经常被问的面试题。如下图所示,可以将柱状图表示为一个数组[2,1,5,6,2,3],每个柱子的宽度假设是一个单位。
求出的最大的矩形应该如下斜线所示,大小是10。
最容易想到的解法是brute force
class Solution:
def largestRectangleArea(self, heights):
"""
:type heights: List[int]
:rtype: int
"""
maxH = 0
for i in range(len(heights)):
left = i
while left >= 0 and heights[left] >= heights[i]:
left -= 1
right = i
while right <= len(heights) - 1 and heights[right] >= heights[i]:
right += 1
temp = (right - left - 1) * heights[i]
if temp > maxH:
maxH = temp
return maxH
A = Solution()
res = A.largestRectangleArea([2,1,5,6,2,3])
print(res)
然而,最神奇的基于堆栈的O(n)解法基本思路(如下图):从第一个柱子(编号1)开始,找出所有的后一个比前一个上手的柱子(从编号1到13),直到遇到一个高度下降的柱子(编号14)。而且把下降之前的编号(从1到13)推入到一个堆栈中。然后计算栈顶编号柱子的高度(所有A1A2线之上的柱子们,从编号3到13)比下降柱子(14)高的所有矩形的面积(因为14不可能和比它高的柱子组成更大的矩形),保留最大的。从栈顶开始,比如,13,12-13,...,8-13,7-13,...,直到3-13。但是14能参与比它矮的较远的编号2(2-14,就是B1B2)和编号1(1-14,就是C1C2)组成矩形。可以看出,堆栈中保留了到当前为止,所有将要参与可能矩形计算的所有柱子的编号。有了这些编号,我们可以计算所有可能的矩形的面积,从而求出最大的。注意了,堆栈中的编号的柱子高度时递增的!
用python实现的代码看上去很简洁。
class Solution:
def largestRectangleArea(self, heights):
stack = []
index = 0
heights.append(-1)
maxH = 0
while index < len(heights):
if len(stack) == 0 or heights[stack[-1]] <= heights[index]:
stack.append(index)
index += 1
else:
area = 0
top = stack.pop()
if len(stack) == 0:
area = heights[top] * index
else:
area = heights[top] * (index - stack[-1] - 1)
if area > maxH:
maxH = area
return maxH
A = Solution()
res = A.largestRectangleArea([2,1,5,6,2,3])
print(res)
假设用例柱状图数组是[2,1,5,6,2,3]。具体解释代码的运行过程,有助于理解。
初始时堆栈为空,将编号为0推入堆栈(是推入编号,不是高度,堆栈中保存的是到当前为止可以参与矩形的最左边柱子),这时的堆栈是[0]。移动到下一个索引编号1。
这时栈顶中编号的柱子(编号0)高于当前正在处理的编号1的柱子的高度,需要弹出堆栈,计算第一个矩形的面积,就是下图的蓝色矩形,这是当前的最大矩形。然后移动到下一个柱子。
这是堆栈为空,将当前柱子的编号1推入堆栈[1]。由于编号1,2,3的柱子的高度是递增的,所以持续推入堆栈[1,2,3]。直到遇到编号为4的柱子,高度下降。这是,要开始弹出堆栈和计算可能的所有当前矩形的面积了。
首先弹出栈顶元素,编号3,这是堆栈为[1,2],然后计算下图绿色矩形面积,看是不是当前最大矩形。
这时栈顶编号2柱子比当前index=4的柱子高,接着弹出栈顶元素2,此时堆栈为[1],然后计算粉色矩形的面积,看是不是当前最大。
这时栈顶编号1的柱子的高度比当前index=4的柱子矮,那么需要将编号4,5,推入堆栈,这时堆栈为[1,4,5],直到遇见最后一个高度为-1的标志柱子。
这时需要计算以堆栈中[1,4,5](注意了,堆栈中编号柱子的高度时递增的)所有编号为最左边的矩形的面积。弹出编号5,计算蓝绿色矩形面积;接着弹出4,计算黄色矩形面积;最后弹出编号1,计算深红色矩形面积。到此为止,完成了所有的计算,找出了最大矩形。
从以上代码流程可以看到,堆栈中的元素的编号始终保持高度是递增,当遇到高度下降的柱子时,依次弹出并计算以栈顶编号柱子为最左边的矩形的面积,直到堆栈中编号的柱子的高度小于当前正在处理的柱子。