LeetCode: Largest Rectangle in Histogram(直方圖最大面積)


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;  
}  

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM