「程式碼隨想錄演算法訓練營」第四十二天 | 單調棧 part2

云雀AC了一整天發表於2024-08-20

42. 接雨水

題目連結:https://leetcode.cn/problems/trapping-rain-water/
文章講解:https://programmercarl.com/0042.接雨水.html
題目難度:困難
影片講解:https://www.bilibili.com/video/BV1uD4y1u75P/
題目狀態:這道題目在LeetCode Top100中做過,使用兩種方法,再回顧一下

思路一:單調棧

  1. 棧的作用

    • 棧用於儲存柱子的索引,確保棧中的高度是遞減的。
  2. 遍歷陣列

    • 對於每個柱子,如果當前柱子高度大於棧頂柱子高度,說明可以形成一個凹槽來積水。
  3. 計算積水

    • 彈出棧頂元素,作為凹槽的底部。
    • 如果棧為空,說明沒有左邊界,無法積水。
    • 否則,計算左邊界(新的棧頂)與當前柱子之間的寬度。
    • 計算高度差:min(height[left], height[i]) - height[top]
    • 計算當前積水量並累加到結果中。
  4. 繼續遍歷

    • 將當前柱子的索引壓入棧中,繼續處理下一個柱子。

程式碼一:

class Solution {
public:
    int trap(vector<int>& height) {
        int ans = 0;
        stack<int> stk;
        int n = height.size();
        for(int i = 0; i < n; ++i) {
            while(!stk.empty() && height[i] > height[stk.top()]) {
                int top = stk.top();
                stk.pop();
                if(stk.empty()) break;
                int left = stk.top();
                int currWidth = i - left - 1;
                int currHeight = min(height[left], height[i]) - height[top];
                ans += currWidth * currHeight;
            }
            stk.push(i);
        }
        return ans;
    }
};

消耗一:

image

思路二:動態規劃

  1. 初始化

    • 檢查輸入陣列是否為空。如果是,直接返回0。
  2. 計算左邊最大高度

    • 建立一個陣列 leftMax,其中 leftMax[i] 儲存從位置0到位置i的最大高度。
    • 透過遍歷陣列,逐步更新 leftMax
  3. 計算右邊最大高度

    • 建立一個陣列 rightMax,其中 rightMax[i] 儲存從位置i到陣列末尾的最大高度。
    • 透過反向遍歷陣列,逐步更新 rightMax
  4. 計算總積水量

    • 遍歷每個位置,計算當前位置能儲存的水量:min(leftMax[i], rightMax[i]) - height[i]
    • 將每個位置的水量累加到總水量中。
  5. 返回結果

    • 返回總積水量。

程式碼二:

class Solution {
public:
    int trap(vector<int>& height) {
        int n = height.size();
        if(n == 0) return 0;
        
        vector<int> leftMax(n);
        leftMax[0] = height[0];
        for(int i = 1; i < n; ++i) {
            leftMax[i] = max(leftMax[i - 1], height[i]);
        }

        vector<int> rightMax(n);
        rightMax[n - 1] = height[n - 1];
        for(int i = n - 2; i >= 0; --i) {
            rightMax[i] = max(rightMax[i + 1], height[i]);
        }

        int ans = 0;
        for(int i = 0; i < n; ++i) {
            ans += min(leftMax[i], rightMax[i]) - height[i];
        }
        return ans;
    }
};

消耗二:

image

84. 柱狀圖中最大的矩形

題目連結:https://leetcode.cn/problems/largest-rectangle-in-histogram/
文章講解:https://programmercarl.com/0084.柱狀圖中最大的矩形.html
題目難度:困難
影片講解:https://www.bilibili.com/video/BV1Ns4y1o7uB/
題目狀態:不會做,看題解

思路一:雙指標

  1. 初始化:

    • minLeftIndex[0] 初始化為 -1,表示最左邊界。
    • minRightIndex[size - 1] 初始化為 size,表示最右邊界。
  2. 計算 minLeftIndex:

    • 從左到右遍歷柱子,對於每個柱子 i,向左尋找第一個高度小於 heights[i] 的柱子。
    • 使用變數 ti-1 開始向左查詢,更新 minLeftIndex[i] 為找到的下標。
    • 如果當前柱子 t 的高度大於等於 heights[i],繼續向左查詢 minLeftIndex[t]
  3. 計算 minRightIndex:

    • 從右到左遍歷柱子,對於每個柱子 i,向右尋找第一個高度小於 heights[i] 的柱子。
    • 使用變數 ti+1 開始向右查詢,更新 minRightIndex[i] 為找到的下標。
    • 如果當前柱子 t 的高度大於等於 heights[i],繼續向右查詢 minRightIndex[t]
  4. 計算最大矩形面積:

    • 遍歷每個柱子 i,計算以該柱子為高的最大矩形面積:heights[i] * (minRightIndex[i] - minLeftIndex[i] - 1)
    • 更新 result 為所有計算出的矩形面積的最大值。
  5. 返回結果:

    • 返回 result,即最大矩形的面積。

程式碼一:

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        int len = heights.size();
        vector<int> minLeftIndex(len);
        vector<int> minRightIndex(len);
        minLeftIndex[0] = -1;
        for(int i = 1; i < len; ++i) {
            int t = i - 1;
            while(t >= 0 && heights[t] >= heights[i]) t = minLeftIndex[t];
            minLeftIndex[i] = t;
        }
        minRightIndex[len - 1] = len;
        for(int i = len - 2; i >= 0; --i) {
            int t = i + 1;
            while(t < len && heights[t] >= heights[i]) t = minRightIndex[t];
            minRightIndex[i] = t;
        }
        int ans = 0;
        for(int i = 0; i < len; ++i) {
            int sum = heights[i] * (minRightIndex[i] - minLeftIndex[i] - 1);
            ans = max(sum, ans);
        }
        return ans;
    }
};

消耗一:

image

思路二:單調棧

  1. 初始化棧:

    • 插入 0 後,棧中初始含有下標 0
  2. 遍歷柱子:

    • 從下標 1 開始遍歷 heights 陣列。
  3. 處理情況:

    • heights[i] < heights[st.top()] 時,表示可以計算以棧頂柱子為高的矩形面積。
    • 不斷彈出棧頂元素,直到棧為空或棧頂柱子高度不大於當前柱子高度。
    • 計算面積:
      • mid 是當前彈出的柱子下標。
      • w = i - st.top() - 1 是矩形的寬度。
      • h = heights[mid] 是矩形的高度。
      • 更新 result 為最大值。
  4. 返回結果:

    • 返回 result,即最大矩形的面積。

程式碼二:

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        int ans = 0;
        stack<int> st;
        heights.insert(heights.begin(), 0);
        heights.push_back(0);
        st.push(0);
        for(int i = 1; i < heights.size(); ++i) {
            if(heights[i] >= heights[st.top()]) st.push(i);
            else {
                while(!st.empty() && heights[i] < heights[st.top()]) {
                    int mid = st.top();
                    st.pop();
                    if(!st.empty()) {
                        int left = st.top();
                        int right = i;
                        int w = right - left - 1;
                        int h = heights[mid];
                        ans = max(ans, w * h);
                    }
                }
                st.push(i);
            }
        }
        return ans;
    }
};

消耗二:

image

相關文章