[LeetCode] LeetCode中柱形圖、矩形等題小結(單調棧)

84. 柱狀圖中最大的矩形

題目鏈接

解題思路: 這裏提供兩種解題思路,即直接解題,和單調棧解題。

  1. 直接解題,觀察下圖,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;
    }
};
  1. 單調棧解法,參考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;
    }
};

————————————

寫在後面

  1. 這兩題屬於矩形中求面積的題,用暴力解法很好解但是時間複雜度很高,OJ會TLE,因此需要選取巧方法解題;
  2. 題85是題84的升級版,一個是一維題,另一個是二維題;
  3. 縱觀這兩題的多個解法,我們會發現,解法都會圍繞橫向擴展、縱向擴展做文章,i.e.,會探索當前點與左右或者上下點的關係,而往往這種題,比較適合用單調棧解題,因爲單調棧就是探討一段連續序列之間的關係.

————————————

參考資料

https://www.cnblogs.com/grandyang/p/4322653.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章