84. 柱狀圖中最大的矩形
解題思路: 這裏提供兩種解題思路,即直接解題,和單調棧解題。
- 直接解題,觀察下圖,6能覆蓋5出現的情況,而5不能覆蓋6的情況,因此我們會想到,在序列遞增的時候可以延遲找最大矩形,而當序列開始遞減時,即出現拐點,從拐點處搜索到序列起點,計算並記錄這個過程出現的最大矩形,矩形的高度是向左遍歷過程中的最小值。
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int res = 0;
heights.push_back(0);
int n = heights.size();
for (int i = 0; i < n - 1; ++i) {
if (heights[i] > heights[i + 1]) {
int minH = heights[i];
for (int j = i; j >= 0; --j) {
minH = min(minH, heights[j]);
res = max(res, minH * (i - j + 1));
}
}
}
return res;
}
};
- 單調棧解法,參考grandyang博客解法,分析解法1會發現,我們在遍歷的過程中維護了一個遞增序列,當出現遞減元素時,開始計算(找尋)最大矩形,那麼聯想到單調棧解法,選取構造單調遞增棧可解此題。具體的是,當棧爲空或棧頂元素高度小於當前元素高度,則入棧,否則說明出現拐點,依次出棧計算以棧頂元素高度能構成的最大矩形。設計代碼中有兩點要注意:當出棧後,(1)棧爲空時則說明出棧的元素的高度是當前訪問序列中最小高度,這意味着矩形的左邊界直達序列的起點,(2)當棧不爲空時,我們要找尋這個出棧元素的高度對應的矩形最左邊能到什麼位置?最左邊是當前棧頂元素對應的位置+1,而不是idx位置,舉個例子,輸入序列爲[4,2,0,3,2,5],組成的圖形如下圖,觀察箭頭所指區域,0觸發找尋矩形,當我們出棧2元素時,高度爲2的矩形左邊界並不是只能到2,而是可以到左邊的0,而我們分析一下這個0代表什麼?0此時正處於棧頂,表示着我們在找2對應的矩形最左邊界時,本質上是要找往左邊看比它小的第一個元素(而這中間如果出現比2大的元素,比如3,對應的矩形面積都能收納到2中),那這個元素是不是恰好是棧頂元素0,其實並沒有什麼巧合,因爲本身我們在遍歷的過程中維護的單調棧就是存放遞增序列的。OK,所有的細節都對上號了~,請看代碼。
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int res = 0;
heights.push_back(0);
int n = heights.size();
stack<int> st;
for (int i = 0; i < n; ++i) {
if (st.empty() || heights[st.top()] < heights[i]) st.push(i);
else {
while (!st.empty() && heights[st.top()] > heights[i]) {
int idx = st.top(); st.pop();
int tmp;
if (st.empty()) tmp = heights[idx] * i;
else tmp = heights[idx] * (i - st.top() - 1);
res = max(res, tmp);
}
st.push(i);
}
}
return res;
}
};
85. 最大矩形
解題思路: 在理解了題84後解此題就非常容易了,即將此題轉換到題84的場景中,先分析示例,當我們將每一層作爲直方圖的底,向上構造直方圖,然後利用題84中的方法求這個直方圖能構成的最大矩形。構造直方圖的方法是,遍歷原數組每層元素時,若該元素爲"1"則直方圖在該位置柱子的高度爲上一層的+1,若該元素爲0,則直方圖柱子高度爲0.詳細內容閱讀grandyang博客.
class Solution {
public:
int maximalRectangle(vector<vector<char>>& matrix) {
if (matrix.empty() || matrix[0].empty()) return 0;
int rows = matrix.size(), cols = matrix[0].size();
vector<int> heights(cols, 0);
int res = 0;
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < cols; ++j) {
if (matrix[i][j] == '0') heights[j] = 0;
else heights[j] += 1;
}
res = max(res, helper(heights));
}
return res;
}
int helper(vector<int> heights) {
int res = 0;
heights.push_back(0);
int n = heights.size();
for (int i = 0; i < n - 1; ++i) {
if (heights[i] > heights[i + 1]) {
int minH = heights[i];
for (int j = i; j >= 0; --j) {
minH = min(minH, heights[j]);
res = max(res, minH * (i - j + 1));
}
}
}
return res;
}
};
另外,在grandyang博客中也給出了另一種解法,方法也挺新穎,這裏記錄一下。我們在解本題時,有時腦子裏也會冒出這種想法,當我們以某個點爲中心時,向兩邊延展,如果能找到延展的左邊界和右邊界,那麼兩邊界的間距作爲矩形的寬,即可用當前點所在的高度構造矩形。那麼解題的關鍵是如何找出矩形的寬(i.e.,找出左邊界和右邊界),矩形的高度依然採用解法1中的方法。某點爲中心向兩邊能延展要符合的條件是,兩邊的高度不能低於當前點高度(否則不能構成矩形),OK,解法是不是有點像
題84中解法1的方法,用高度作爲判斷,若探索左邊界,在從當前點i向左探索,當高度比當前高度低時break,右邊界同理。
class Solution {
public:
int maximalRectangle(vector<vector<char>>& matrix) {
if (matrix.empty() || matrix[0].empty()) return 0;
int rows = matrix.size(), cols = matrix[0].size();
vector<int> heights(cols, 0);
int res = 0;
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < cols; ++j) {
if (matrix[i][j] == '0') heights[j] = 0;
else heights[j] += 1;
}
res = max(res, helper(heights, i, matrix));
}
return res;
}
int helper(vector<int> heights, int row, vector<vector<char>>& matrix) {
int cols = matrix[0].size();
vector<int> left(cols), right(cols);
for (int i = 0; i < cols; ++i) {
for (int j = i; j >= 0; --j) {
if (heights[j] >= heights[i]) {
left[i] = j;
} else break;
}
}
for (int i = cols - 1; i >= 0; --i) {
for (int j = i; j < cols; ++j) {
if (heights[j] >= heights[i]) {
right[i] = j;
} else break;
}
}
int res = 0;
for (int i = 0; i < cols; ++i) {
res = max(res, heights[i] * (right[i] - left[i] + 1));
}
return res;
}
};
另外grandyang給出了第三種解法,從另一個角度解題,解法2是橫向擴展,在題84heights的基礎上解題,而解法三採取的縱向擴展,先求出每個元素水平向左連續的"1"個數,即Ones[i][j]
,然後再訪問一次每個元素,然後對訪問元素向上擴展,結合下圖解釋,藍色爲1,綠色爲0,假設我們當訪問的位置是箭頭起點,然後順着箭頭方向向上擴展,向上擴展的過程中,要取ones[x][j]
路徑上的最小值作爲矩形寬度,對於A所在位置,應該取1,而不是去min(2,3),即去路徑訪問當前最小值。
class Solution {
public:
int maximalRectangle(vector<vector<char>>& matrix) {
if (matrix.empty() || matrix[0].empty()) return 0;
int rows = matrix.size(), cols = matrix[0].size();
vector<vector<int>> Ones(rows, vector<int>(cols, 0));
int res = 0;
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < cols; ++j) {
if (matrix[i][j] == '1') Ones[i][j] = 1 + (j == 0 ? 0 : Ones[i][j - 1]);
}
}
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < cols; ++j) {
if (matrix[i][j] == '0') continue;
res = max(res, Ones[i][j]);
int mn = Ones[i][j];
for (int k = i - 1; k >= 0 && matrix[k][j] != '0'; --k) {
mn = min(mn, Ones[k][j]);
res = max(res, mn * (i - k + 1));
}
}
}
return res;
}
};
————————————
寫在後面
- 這兩題屬於矩形中求面積的題,用暴力解法很好解但是時間複雜度很高,OJ會TLE,因此需要選取巧方法解題;
- 題85是題84的升級版,一個是一維題,另一個是二維題;
- 縱觀這兩題的多個解法,我們會發現,解法都會圍繞橫向擴展、縱向擴展做文章,i.e.,會探索當前點與左右或者上下點的關係,而往往這種題,比較適合用單調棧解題,因爲單調棧就是探討一段連續序列之間的關係.
————————————
參考資料
https://www.cnblogs.com/grandyang/p/4322653.html