84. Largest Rectangle in Histogram
Title
給定 n 個非負整數(shù),用來表示柱狀圖中各個柱子的高度。每個柱子彼此相鄰,且寬度為 1 。
求在該柱狀圖中,能夠勾勒出來的矩形的最大面積。
以上是柱狀圖的示例,其中每個柱子的寬度為 1,給定的高度為 [2,1,5,6,2,3]。
圖中陰影部分為所能勾勒出的最大矩形面積,其面積為 10 個單位。
示例:
輸入: [2,1,5,6,2,3]
輸出: 10
Solve
Violence
暴力的方法就比較好寫了,直接枚舉矩形的寬和高,其中寬表示矩形貼著柱狀圖底邊的寬度,高表示矩形在柱狀圖上的高度。
雙重循環(huán)枚舉矩形的左右邊界以固定寬度w,高度為所有包含在內(nèi)柱子的最小高度h,對應(yīng)的面積為w * h:
def largestRectangleArea_Violence_Width(self, heights: List[int]) -> int:ans, length = 0, len(heights)for i in range(length):minHeight = max(heights)for j in range(i, length):minHeight = min(minHeight, heights[j])ans = max((j - i + 1) * minHeight, ans)return ans還有一種暴力枚舉的方法,我們使用兩層循環(huán)枚舉寬,其實可以通過一層循環(huán)枚舉高,將其固定為矩形的高度h,然后從這根柱子開始向兩側(cè)延伸,直到遇到高度小于h的柱子,就確定了矩形的左右邊界,即寬w,對應(yīng)的面積就是w * h:
def largestRectangleArea_Violence_Height(self, heights: List[int]) -> int:ans, length = 0, len(heights)for middle in range(length):height = heights[middle]left, right = middle, middlewhile left - 1 > -1 and heights[left - 1] >= height:left -= 1while right + 1 < length and heights[right + 1] >= height:right += 1ans = max(ans, (right - left + 1) * height)return ans兩種暴力枚舉方法的時間復(fù)雜度均為O(N2),會超出時間顯示,枚舉寬度的方法使用了兩層循環(huán),沒有什么優(yōu)化的空間了,所以只能優(yōu)化枚舉高度的一層循環(huán)。
單調(diào)棧
如果有兩根柱子j0和j1,其中j0在j1的左側(cè),并且j0的高度大于等于j1,那么在后面的柱子i向左找小于其高度柱子時,j1會擋住j0。
這樣以來,可以對數(shù)組從左向右進行遍歷,同時維護一個可能答案的數(shù)據(jù)結(jié)構(gòu),其中按照從小到大的順序存放一些j值,如果我們存放了j0,j1,…,js,一定有heights[j0]<heights[j1]<…<heights[js]。
當我們枚舉到第i根柱子時,數(shù)據(jù)結(jié)構(gòu)中存放了j0,j1,…,js,如果第i根柱子左側(cè)且最近的小于其高度的柱子為ji,那么必然有:
height[j0]<height[j1]<?<height[ji]<height[i]≤height[ji+1]<?<height[js]height[j_0]<height[j_1]<?<height[j_i]<height[i]≤height[j_{i+1}]<?<height[j_s] height[j0?]<height[j1?]<?<height[ji?]<height[i]≤height[ji+1?]<?<height[js?]
當我們枚舉到i+1時,原來的i也變成了j值,因此i會被放入數(shù)據(jù)結(jié)構(gòu)。由于所有在數(shù)據(jù)結(jié)構(gòu)中的j值均小于i,那么所有高度大于等于height[i]的j都不會作為答案,需要從數(shù)據(jù)結(jié)構(gòu)中移除,恰好就是ji+1,…,js。
這樣我們在枚舉到第 i 根柱子的時候,就可以先把所有高度大于等于 height[i] 的 j 值全部移除,剩下的 j 值中高度最高的即為答案。
在這之后,我們將 i 放入數(shù)據(jù)結(jié)構(gòu)中,開始接下來的枚舉。此時,我們需要使用的數(shù)據(jù)結(jié)構(gòu)也就呼之欲出了,它就是棧。
- 棧中存放了 j 值。從棧底到棧頂,j 的值嚴格單調(diào)遞增,同時對應(yīng)的高度值也嚴格單調(diào)遞增;
- 當我們枚舉到第 i 根柱子時,我們從棧頂不斷地移除 height[j]≥height[i] 的 j 值。在移除完畢后,棧頂?shù)?j 值就一定滿足 height[j]<height[i],此時 j 就是 i 左側(cè)且最近的小于其高度的柱子。
- 如果我們移除了棧中所有的 j 值,那就說明 i 左側(cè)所有柱子的高度都大于 height[i],可以認為 i 左側(cè)且最近的小于其高度的柱子在位置 j=-1,一根「虛擬」的、高度無限低的柱子。
- 再將 ii 放入棧頂
棧中存放的元素具有單調(diào)性,這就是經(jīng)典的數(shù)據(jù)結(jié)構(gòu)「單調(diào)棧」了。
例子:[6,7,5,2,4,5,9,3]
初始時的棧為空。
6:因為棧為空,所以 6 左側(cè)的柱子是「哨兵」,位置為 -1。隨后我們將 6 入棧。
- 棧:[6(0)]。(這里括號內(nèi)的數(shù)字表示柱子在原數(shù)組中的位置)
7:由于 6<7,因此不會移除棧頂元素,所以 6 左側(cè)的柱子是 7,位置為 0。隨后我們將 7 入棧。
- 棧:[6(0), 7(1)]
5:由于 7≥5,因此移除棧頂元素 7。同樣地,6≥5,再移除棧頂元素 6。此時棧為空,所以 5 左側(cè)的柱子是「哨兵」,位置為 ?1。隨后我們將 5 入棧。
- 棧:[5(2)]
2:移除棧頂元素 5,得到 2 左側(cè)的柱子是「哨兵」,位置為 ?1。將 2 入棧。
- 棧:[2(3)]
4,5 和 9:不會移除任何棧頂元素,得到它們左側(cè)的柱子分別是 2,4 和 5,位置分別為 3,4 和 5。將它們?nèi)霔!?/p>
- 棧:[2(3), 4(4), 5(5), 9(6)]
3:依次移除棧頂元素 9,5 和 4,得到 3 左側(cè)的柱子是 2,位置為 3。將 3 入棧。
- 棧:[2(3), 3(7)]
這樣以來,我們得到它們左側(cè)的柱子編號分別為 [-1, 0, -1, -1, 3, 4, 5, 3]。用相同的方法,我們從右向左進行遍歷,也可以得到它們右側(cè)的柱子編號分別為 [2, 2, 3, 8, 7, 7, 7, 8],這里我們將位置 8 看作「哨兵」。
每一個位置只會入棧一次(在枚舉到它時),并且最多出棧一次。因此當我們從左向右/總右向左遍歷數(shù)組時,對棧的操作的次數(shù)就為 O(N)。所以單調(diào)棧的總時間復(fù)雜度為 O(N)。
Code
def largestRectangleArea_MonotoneStack(self, heights: List[int]) -> int:length = len(heights)left, right = [0] * length, [0] * lengthmonotoneStack = list()for i in range(length):while monotoneStack and heights[monotoneStack[-1]] >= heights[i]:monotoneStack.pop()left[i] = monotoneStack[-1] if monotoneStack else -1monotoneStack.append(i)monotoneStack = list()for i in range(length - 1, -1, -1):while monotoneStack and heights[monotoneStack[-1]] >= heights[i]:monotoneStack.pop()right[i] = monotoneStack[-1] if monotoneStack else lengthmonotoneStack.append(i)ans = max((right[i] - left[i] - 1) * heights[i] for i in range(length)) if length > 0 else 0return ans總結(jié)
以上是生活随笔為你收集整理的84. Largest Rectangle in Histogram的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 记录第一次面试
- 下一篇: LeetCode Algorithm 1