Leetcode題解 Python: Largest Rectangle in Histogram

直方圖中最大的長方形面積

這題感到棘手,寫好的暴力破解法會超時,只能不斷看題目去領悟點甚麼。

找出最大面積不是件難事,要怎麼才能加速呢?

看了搜尋的兩前項,別人寫著遞增數列,找局部峰值,為什麼?到底有甚麼關係?

我簡單一句講明目的:「把這張直方圖分切出各個好處理的部分。」


在這個遞迴II章節,有一個部分是在說到把問題切碎,處理後再重組,得到答案。這就是需要這樣做的例子。

直方圖分解示意圖

把局部峰值也就是左大右小之處,切下去,左邊的值會遞減。得到一個遞增數列再往右搜尋。

碰到下一個峰值,往左遇到實際較高的直方,因為它已經被切進上一個遞增數列,所以可以忽視掉突出部分,維持一樣的值繼續遞減。

一旦變成遞增數列就可以很快速地找到最大面積。

把直方圖的每直方看成座標,遞增數列的右下角為原點,找出端點x和y絕對值相乘最大的就是該部分最大面積。

得到各部分的最大面積,再相互比較,就能得到直方圖中最大的長方形面積。

class Solution:
"""遞增數列"""
def largestRectangleArea(self, heights) -> int:
if not heights: return 0
peaks = []
p = None
for idx, each in enumerate(heights):
if p and each < p:
peaks.append(idx-1)
p = each
peaks.append(n-1)

maxAreas = []
while peaks:
# 從右而左搜尋
endpoint = peaks.pop()
height = heights[endpoint]
maxArea = 0
for i in range(endpoint,-1,-1):
width = endpoint+1-i
height = min(height, heights[i])
maxArea = max(maxArea, width*height)
maxAreas.append(maxArea)
return max(maxAreas)

先找峰值,紀錄峰值出現在哪,最後一個也是峰值,最後從記錄找起一次把各種切出遞增數列找出最大長方形面積,再得到當中的最大值。

接下來就是優化了,對於一個 N 個數列,首先先減少遍例次數,求各遞增數列的最大面積可以放在找峰值的時候進行。

而且如果左右兩個點一樣高,可以忽略右邊的不計,減少要找的端點。在遍歷各端點時,如果剩下的最大可能面積沒辦法比較大,就可以跳出迴圈。

經過一番優化之後,就變成這樣。

這讓我深刻體會到,unfold 的好處在哪裡。如果沒有標上註解,可讀性遠遠不如上一個。

class Solution:
"""遞增數列"""
def largestRectangleArea(self, heights) -> int:
if not heights: return 0
def getMaxArea():
area = 0
endpoint, height = hArray.pop()
for i in range(len(hArray)-1,-1,-1):
idx, h2 = hArray[i]
width = endpoint+1-idx
height = min(height, h2)
area = max(area, width*height)
if (endpoint+1)*height <= area:break
return area


n = len(heights)
hArray= []
maxAreas = []

for idx, h in enumerate(heights):
if idx:
if h < preH: #出現峰值,即刻求該遞增數列的最大面積
# 添加EndPoint
hArray.append((idx-1, preH))
# 搜尋當前面積
maxAreas.append(getMaxArea())
# 砍掉前面鋒值凸出的部分,最後要添加端點紀錄修改
stack = [idx, h]
while hArray and hArray[-1][1] > h:
stack[0] = hArray[-1][0]
del hArray[-1]
hArray.append(stack)
elif h > preH: #紀錄遞增變化
hArray.append((idx, h))
else:
hArray.append((idx, h))
preH = h
# 最後一搜,其中一個為最後一直方的左上端點 另一個是右上
hArray.extend([(idx, h), (idx, h)])
maxAreas.append(getMaxArea())
return max(maxAreas)