LeetCode 84 - Largest Rectangle in Histogram
題目
題目連結:https://leetcode.com/problems/largest-rectangle-in-histogram/
給定 N 個長條圖的條的高度,每個條的寬度為 1,求最大的長方形面積。
範例說明
1 | Example: |

想法
時間複雜度 O(N)
首先,最大的矩形之高度一定是以某一個條的高度為高,向左右延伸直到不能再延伸為止。
要證明這個特性並不難,若當前的矩形不以任何一個條的高度為高,則矩形可以增高、變得更大。若當前矩形還可以向左右延伸,則矩形可以增加寬度,也會變得更大。所以最大的矩形之高度一定是以某一個條的高度為高,並向左右延伸直到不能再延伸為止。
接著就是要算出以位置 i 的高度 heights[i] 為高,最多可以延伸左右至哪裡。
可以發現,位置 i 向左最多可以延伸直到第一個高度比 heights[i] 小的位置。也就是如果由左到右維護一個遞增的序列,就可以知道向左延伸的極限位置。
舉範例 [2, 1, 5, 6, 2, 3] 來說,一開始遞增序列為空:
[2]:2加入序列,序列保持遞增。數字2可以延伸至最左[1]:1加入序列,為了讓序列保持遞增,必須將2移除。數字1可以延伸至最左[1, 5]:5加入序列,序列保持遞增。數字5可以延伸至數字1之右側[1, 5, 6]:6加入序列,序列保持遞增。數字6可以延伸至數字5之右側[1, 2]:2加入序列,為了讓序列保持遞增,將6,5依序移除。數字2可以延伸至數字1之右側[1, 2, 3]:3加入序列,序列保持遞增。數字3可以延伸至數字2之右側
因為上述的序列操作中,數字只會從序列的最右邊加入,且要維護遞增序列即是比較序列的右側與當前值,若當前值較小則不斷移除序列右側元素。這種先進後出(FILO)的性質可以利用一個堆疊(Stack)來維護。
反之,要計算位置 i 向右最多可以延伸至多少,則由右至左維護一個遞增的序列。
可以發現,由左至右,一個元素最多會被加入、移除 stack 一次。由右至左也是。所以總時間複雜度為 O(N)。
優化到 One Pass O(N) 的想法
2021/03/15 更新
原想法為先由左到右求出當前高度 heights[i] 能夠向左延伸到哪裡,再由右到左求出 heights[i] 向右能延伸到哪裡。
但其實 heights[i] 能向右延伸到哪裡不需要由右到左來求出,可以發現由左到右維護一個遞增的序列時,若當前的數字為 y 且數字 x 要從序列中被移除時,則 x 最多就能向右延伸到 y。
詳細做法見最後的程式碼。
實作細節
筆者原用 std::stack 來實作,不過速度較慢,所以改為使用一陣列配合一指標來模擬 stack 的運作。因此以下以講解陣列模擬 stack 的版本為主,但 std::stack 之版本也會在下面附上。
stk 為維護遞增序列用之堆疊,idx 為當前 stack 的頂端元素的索引值。其中 idx = 0 代表 stack 為空,而 stack 內的元素從索引值 1 開始放置。注意筆者是將序列的索引加入 stk 之中,而非高度。
利用 left 陣列保存位置 i 最多可以延伸多少長度。
由左至右,當 stack 不為空且其頂端元素大於等於當前的高度,則為了要保持嚴格遞增,將 stack 之頂端元素移除,並再次檢查。也就是:
1 | while (idx && heights[stk[idx]] >= heights[i]) --idx; |
接著,若 stack 為空,則當前元素可以向左延伸至最左,其長度為 i + 1;若 stack 不為空,則當前元素可以向左延伸至 stack 內的頂端元素位置的右側,其長度為 i - stk[idx]。也就是:
1 | if (!idx) left[i] = i + 1; |
最後,將當前的索引加入序列之中,也就是:
1 | stk[++idx] = i; |
計算向右延伸之長度時,則由右到左維護遞增序列。但是筆者沒有紀錄 right[i] 而是一計算出向右延伸之長度 right 後,直接計算當前面積 (right + left[i] - 1) * heights[i]。注意因為向左、向右延伸之長度都會覆蓋到自己本身,所以要減一。
程式碼
陣列模擬 Stack
1 | /** |
std::stack
1 | /** |
One Pass O(N) with std::stack
2021/03/15 更新
1 | /** |