有兩種方法,左右掃描或輔助棧。
方法I: 左右掃描法
考慮到最大面積的矩形高度一定跟某個條一樣高,所以挨個枚舉每個條,看其向左、向右最多能延伸到多遠。在計算左右邊界時,可以借助之前計算過的結果迭代(類似動歸的感覺)優化以減少時間復雜度,這應該算是唯一的難點了。總的來說,向左一遍,向右一遍,整體求面積再一遍,一共需要3次遍歷,時間復雜度是O(n)。
左右掃描法非常直觀。
代碼:
1 int largestRectangleArea(vector<int> &height) { 2 if (height.empty()) return 0; 3 4 int n = height.size(); 5 int maxArea = 0; 6 int *left = new int[n]; // 向左能延伸多遠 7 int *right = new int[n]; // 向右能延伸多遠 8 9 // 向右延伸 10 right[n - 1] = 1; 11 for (int i = n - 2; i >= 0; i--) { 12 if (height[i] > height[i + 1]) 13 right[i] = 1; 14 else { 15 int j = i + 1; 16 while (j < n && height[j] >= height[i]) 17 j += right[j]; 18 right[i] = j - i; 19 } 20 } 21 22 // 向左延伸 23 left[0] = 1; 24 for (int i = 1; i < n; i++) { 25 if (height[i] < height[i - 1]) 26 left[i] = 1; 27 else { 28 int j = i - 1; 29 while (j >= 0 && height[j] >= height[i]) 30 j -= left[j]; 31 left[i] = i - j; 32 } 33 } 34 35 // 求面積 36 maxArea = height[0]; 37 for (int i = 0; i < n; i++) { 38 maxArea = max(height[i] * (left[i] + right[i] - 1), maxArea); 39 } 40 41 return maxArea; 42 }
方法II: 輔助棧法(網上很多人采用的方法)
根本思想是:依次遍歷所有矩形條,嘗試計算以該矩形條為高度的矩形面積。但是在遍歷的時候我們不知道后面還有什么樣的矩形條怎么辦?沒關系,對於沒法確定面積的矩形,壓棧,留着以后處理,而對於那些已經可以確定計算出面積的矩形條,留着也沒用,彈棧。
如果我們能知道一個矩形條向左向右最遠能延伸多遠,我們就能計算出以該矩形條為高的矩形面積了!我們怎么知道向左向右能延伸多遠?觀察下面幾種情況:
情況1,第i個矩形比右邊相鄰的第i+1個矩形高,如下圖所示。意味着,以height[i]為高的矩形的右邊界就是第i個矩形,因為右邊界不能更右了(廢話),也不會在左邊(向左只會讓矩形面積減小)。所以在這種情況下,我們可以立即確定以第i個矩形的高度height[i]為高度的最大矩形面積的右邊界。
情況2,第i個矩形比左邊相鄰的第i-1個矩形高,如下圖所示。意味着,以height[i]為高的矩形的左邊界就是第i個矩形,因為左邊界不能更左了(廢話),也不會出現在右邊(因為向右只會另矩形面積減小)。所以在這種情況下,我們立即就可以確定以第i個矩形的高度height[i]為高度的最大舉行面積的左邊界。
現在,我們依次遍歷各個矩形條,遍歷過的矩形條壓入棧中保存,則不難發現下面的現象:
如果當前矩形條的高度高於棧頂的矩形條的高度,對應上面的情況2,我們可以立即得出,當前矩形一定是以它為高的矩形的左邊界。那么現在我們還不能確定其右邊界,所以除了入棧什么都不用做,如下圖所示:
如果當前矩形條的高度小於或等於棧頂矩形條的高度,對應上面的情況1,我們可以立即得出:棧頂的矩形一定是以它為高的矩形的右邊界所以,我們可以立刻得到以棧頂的矩形條高度為高度的最大矩形的面積(下圖中帶顏色的兩個矩形)!既然我們算出了前一個條最大矩形的面積,那么也就沒必要再留着它了。所以,可以放心把它刪掉,或者說合並。
按照上述操作遍歷完所有矩形條之后,棧中的矩形一定是下面這個樣子。(棧里面所有的條肯定是按照高度依次遞增的)
此時,對於任何一個矩形條,我們可以確定,它的右邊界一定是整個棧里所保留的條形圖的最右邊界,而它的左邊界一定是它這個條自己的左邊界,所以,剩余的矩形條也可以立即算出其對應的最大矩形的面積。
其實,只需要在所有的矩形條最后添加一個高度為0的虛擬矩形條,可以省略上面的兩小步。這個虛擬矩形條起收割作用。(見代碼第2行)
顯然時間復雜度是O(n)。
具體代碼實現有兩個技巧:
1. 我們只需要在輔助棧保存矩形的右邊界坐標即可,不需要保存高度,因為可以通過右邊界坐標得到(height[i] ),也不需要保存左邊界坐標,因為上一個矩形的右邊界坐標+1就是當前矩形的左邊界。
2. 在直方圖最后添加一個高度為0的虛擬矩形條,這樣保證一次遍歷之后棧里面的矩形都被正確處理過了,否則需要再重復一遍。
代碼:
1 int largestRectangleArea(vector<int> &height) { 2 height.push_back(0); // 添加虛擬矩形條 3 stack<int> st; 4 int n = height.size(); 5 int maxArea = 0; 6 int h, w; 7 8 for (int i = 0; i < n; i++) { 9 if (st.empty() || height[st.top()] < height[i]) 10 st.push(i); 11 else { 12 while (!st.empty() && height[i] <= height[st.top()]) { 13 h = height[st.top()]; 14 st.pop(); 15 w = st.empty() ? i : i - (st.top() + 1); 16 maxArea = max(maxArea, h * w); 17 } 18 st.push(i); 19 } 20 } 21 22 return maxArea; 23 }