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.
Example:
Input: [2,1,5,6,2,3]
Output: 10
思路
此題可理解爲,找出每個小矩形所能擴展的最大矩形的面積
-
暴力解法
注意到每個小矩形能擴展的最大矩形的高就是當前小矩形的高,而寬的長度是需要我們尋找的,這個寬需要滿足的條件爲:寬的左右邊界所對應的小矩形的高度應不低於當前小矩形的高度。
因此可以在遍歷數組中每個元素時,分別向左右兩端遍歷,得到寬進而求出矩形面積,所有面積的最大值即爲所求。
時間複雜度: O(n2)
空間複雜度: O(1) -
單調棧解法
注意到暴力解法是從前往後遍歷每個元素找解的方法,由於每次遍歷的時候,都要找左右邊界,以至於做了很多重複的運算。注意到找左右邊界實際就是要找到左右兩邊最近的不小於當前元素的元素。因此我們可以考慮如下場景:
(1)我們假設當前元素是A[i],每次遍歷的時候,判斷A[i]和A[i+1],如果A[i] < A[i+1],則把A[i]保存下來,繼續遍歷下一個;如果A[i] > A[i+1],那麼A[i+1]就是A[i]的右邊界,而左邊界就是我們保存下來的離A[i]最近的那個元素;
(2)找到左邊界和右邊界後,我們就可以把當前元素的最大矩形計算出來,然後當前元素就可以從保存的元素中除去,此時我們需要考慮,此種場景的適用條件是什麼?適用條件就是保存的元素必須是從小到大排列的,也就是我們需要維護一個遞增序列;
(3)經過以上分析,我們可以意識到我們維護的遞增序列其實就是一個單調棧,我們每次比較A[i]和A[i+1]時,實際是比較的棧頂元素與當前元素,如果棧頂元素大於當前元素,則開始出棧操作。出棧後,出棧元素的值就是最大矩形的高,當前元素與當前棧頂元素的下標的差值減一就是最大矩形的寬。
(4)注意到上述的一般場景對於首元素和尾元素是不適用的,此時我們可以增加首尾兩個哨兵,來將這兩個特殊場景一般化處理。
時間複雜度: O(n)
空間複雜度: O(n)
Java實現
class Solution {
public int largestRectangleArea(int[] heights) {
if (heights == null || heights.length == 0) {
return 0;
}
int length = heights.length;
if (length == 1) {
return heights[0];
}
// 將數組前後加上哨兵,方便首尾特殊情況的處理
int[] arr = new int[length+2];
for (int i = 0; i < length; i++) {
arr[i + 1] = heights[i];
}
length += 2;
heights = arr;
// 定義面積
int area = 0;
// 定義棧
Deque<Integer> stack = new ArrayDeque<>();
// 首哨兵先入棧
stack.push(0);
// 首哨兵不參加遍歷
for (int i = 1; i < length; i++) {
// 由於設置了哨兵,棧永遠都不會空
// 棧頂元素 > 當前元素,進行出棧操作
while (heights[stack.peek()] > heights[i]) {
// 計算高,寬和麪積
int height = heights[stack.pop()];
int width = i - stack.peek() - 1;
area = Math.max(area, height * width);
}
// 跳出循環後意味着:棧頂元素 <= 當前元素,進行入棧操作
stack.push(i);
}
return area;
}
}
Python實現
class Solution(object):
def largestRectangleArea(self, heights):
"""
:type heights: List[int]
:rtype: int
"""
if heights == None or len(heights) == 0:
return 0
length = len(heights)
if length == 1:
return heights[0]
# 設置首尾哨兵
heights.insert(0, 0)
heights.append(0)
length += 2
# 定義面積
area = 0
# 定義棧
stack = []
# 首哨兵入棧
stack.append(0)
# 首哨兵不參加遍歷
for i in range(1, length):
# 由於設置了哨兵,棧永遠都不會空
# 棧頂元素 > 當前元素,進行出棧操作
while heights[stack[-1]] > heights[i]:
height = heights[stack.pop()]
width = i - stack[-1] - 1
area = max(area, height * width)
# 跳出循環後意味着:棧頂元素 <= 當前元素,進行入棧操作
stack.append(i)
return area
Scala實現
import scala.collection.mutable.Stack
import scala.math._
object Solution {
def largestRectangleArea(heights: Array[Int]): Int = {
if (heights == null || heights.length == 0) {
return 0
}
var length = heights.length
if (length == 1) {
return heights(0)
}
// 對原始數組設置首尾哨兵
val heights2 = new Array[Int](length + 2)
for (i <- 0 until length) {
heights2(i+1) = heights(i)
}
length += 2
// 定義面積
var area = 0
// 定義棧
val stack = new Stack[Int]()
// 首哨兵先入棧
stack.push(0)
// 首哨兵不參加遍歷
for (i <- 1 until length) {
// 由於設置了哨兵,棧永遠都不會空
// 棧頂元素 > 當前元素,進行出棧操作
while (heights2(stack.top) > heights2(i)) {
val height = heights2(stack.pop())
val width = i - stack.top - 1
area = max(area, height * width)
}
// 跳出循環後意味着:棧頂元素 <= 當前元素,進行入棧操作
stack.push(i)
}
return area
}
}