http://blog.csdn.net/abcbc/article/details/8943485
具體的題目描述為:
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.
Above is a histogram where width of each bar is 1, given height =[2,1,5,6,2,3]
.
The largest rectangle is shown in the shaded area, which has area =10
unit.
For example,
Given height = [2,1,5,6,2,3]
,
return 10
.
這道題可以有兩個解法。
解法一是窮舉法,對於直方圖的每一個右邊界,窮舉所有的左邊界。將面積最大的那個值記錄下來。時間復雜度為O(n^2). 單純的窮舉在LeetCode上面過大集合時會超時。可以通過選擇合適的右邊界,做一個剪枝(Pruning)。觀察發現當height[k] >= height[k - 1]時,無論左邊界是什么值,選擇height[k]總會比選擇height[k - 1]所形成的面積大。因此,在選擇右邊界的時候,首先找到一個height[k] < height[k - 1]的k,然后取k - 1作為右邊界,窮舉所有左邊界,找最大面積。
Java代碼:
1 // O(n^2) with pruning 2 public int largestRectangleArea1(int[] height) { 3 // Start typing your Java solution below 4 // DO NOT write main() function 5 int area = 0; 6 for (int i = 0; i < height.length; i++) { 7 for (int k = i + 1; k < height.length; k++) { 8 if (height[k] < height[k - 1]) { 9 i = k - 1; 10 break; 11 } else { 12 i = k; 13 } 14 } 15 int lowest = height[i]; 16 for (int j = i; j >= 0; j--) { 17 if (height[j] < lowest) { 18 lowest = height[j]; 19 } 20 int currArea = (i - j + 1) * lowest; 21 if (currArea > area) { 22 area = currArea; 23 } 24 } 25 } 26 return area; 27 }
雖然上面的解法可以過大集合,但是不是最優的方法,下面介紹使用棧的優化解法。時間復雜度為O(n).
此解法的核心思想為:一次性計算連續遞增的區間的最大面積,並且考慮完成這個區間之后,考慮其前、后區間的時候,不會受到任何影響。也就是這個連續遞增區間的最小高度大於等於其前、后區間。
這個方法非常巧妙,最好通過一個圖來理解:
假設輸入直方圖為:int[] height = {2,7,5,6,4}.
這個方法運行的時候,當遇到height[2] == 5的時候,發現其比之前一個高度小,則從當前值(5)開始,向左搜索比當前值小的值。當搜索到最左邊(2)時,比5小,此時計算在height[0]和height[2]之間的最大面積,注意不包括height[0]和和height[2]。height[1]以紅色標出的這個區域就被計算完成。同樣的方法,計算出綠色和粉色的面積。
因此這個方法需要使用兩個棧。第一個棧為高度棧heightStack,用於記錄還沒有被計算過的連續遞增的序列的值。第二個棧為下標棧indexStack,用於記錄高度棧中對應的每一個高度的下標,以計算寬度。
算法具體執行的步驟為:
若heightStack為空或者當前高度大於heightStack棧頂,則當前高度和當前下標分別入站(下面有一個解法可以只用一個棧即可,用棧來保存下標,而高度由下標很容易得到)。所以heightStack記錄了一個連續遞增的序列。
若當前高度小於heightStack棧頂,heightStack和indexStack出棧,直到當前高度大於等於heightStack棧頂。出棧時,同時計算區間所形成的最大面積。注意計算完之后,當前值入棧的時候,其對應的下標應該為最后一個從indexStack出棧的下標。比如height[2]入棧時,其對應下標入棧應該為1,而不是其本身的下標2。如果將其本身下標2入棧,則計算綠色區域的最大面積時,會忽略掉紅色區域。
C++代碼:
class Solution { public: int largestRectangleArea(vector<int> &height) { if(height.size() == 0) return 0; int res = 0; vector<int> tmp = height; tmp.push_back(0); // Important stack<int> s; for(int i = 0; i < tmp.size(); i++) { if(s.empty() || (!s.empty() && tmp[i] >= tmp[s.top()])) s.push(i); else{ while(!s.empty() && tmp[s.top()] > tmp[i]) { int idx = s.top(); s.pop(); int width = s.empty() ? i : (i-s.top()-1); res = max(res, tmp[idx] * width); } s.push(i); // Important } } return res; } };
Java代碼:
// O(n) using two stacks public int largestRectangleArea(int[] height) { // Start typing your Java solution below // DO NOT write main() function int area = 0; java.util.Stack<Integer> heightStack = new java.util.Stack<Integer>(); java.util.Stack<Integer> indexStack = new java.util.Stack<Integer>(); for (int i = 0; i < height.length; i++) { if (heightStack.empty() || heightStack.peek() <= height[i]) { heightStack.push(height[i]); indexStack.push(i); } else if (heightStack.peek() > height[i]) { int j = 0; while (!heightStack.empty() && heightStack.peek() > height[i]) { j = indexStack.pop(); int currArea = (i - j) * heightStack.pop(); if (currArea > area) { area = currArea; } } heightStack.push(height[i]); indexStack.push(j); } } while (!heightStack.empty()) { int currArea = (height.length - indexStack.pop()) * heightStack.pop(); if (currArea > area) { area = currArea; } } return area; }
更新:
在網上發現另外一個使用一個棧的O(n)解法,代碼非常簡潔,棧內存儲的是高度遞增的下標。對於每一個直方圖高度,分兩種情況。1:當棧空或者當前高度大於棧頂下標所指示的高度時,當前下標入棧。否則,2:當前棧頂出棧,並且用這個下標所指示的高度計算面積。而這個方法為什么只需要一個棧呢?因為當第二種情況時,for循環的循環下標回退,也就讓下一次for循環比較當前高度與新的棧頂下標所指示的高度,注意此時的棧頂已經改變由於之前的出棧。
Java代碼:
// O(n) using one stack public int largestRectangleArea(int[] height) { // Start typing your Java solution below // DO NOT write main() function int area = 0; java.util.Stack<Integer> stack = new java.util.Stack<Integer>(); for (int i = 0; i < height.length; i++) { if (stack.empty() || height[stack.peek()] < height[i]) { stack.push(i); } else { int start = stack.pop(); int width = stack.empty() ? i : i - stack.peek() - 1; area = Math.max(area, height[start] * width); i--; } } while (!stack.empty()) { int start = stack.pop(); int width = stack.empty() ? height.length : height.length - stack.peek() - 1; area = Math.max(area, height[start] * width); } return area; }