單調棧分析

無重複元素的單調棧

  • 保證元素在入棧的過程中,做到任意時刻從棧底到棧頂的元素都是單調(遞增或者遞減)的,那麼此種棧就是一個單調棧。
  • 單調棧的性質:
    • 利用其找到數組中左邊最近且小於當前元素的元素右邊最近且小於當前元素的元素
    • 利用其找到數組中左邊最近且大於當前元素的元素右邊最近且大於當前元素的元素
  • 單調棧找到小於(大於)且最近的元素的原理:
    • 棧中存的是數組元素的下標
    • 遞增單調棧中(棧底到棧頂按照從小到大的順序)
      • 當前元素如果大於棧頂,或者棧爲空 入棧
      • 當前元素如果小於棧頂,一直退棧直到當前元素大於棧頂才能入棧
        • 退棧棧頂元素的時候,此時就是計算此退棧元素的左小且最近右小且最近的時刻
        • 讓其退棧的右邊元素就是其右小且最近,若無則是-1(無效下標)
        • 其退棧後,新的棧頂就是其左小且最近,若無則是-1(無效下標)
      • 最後將所有元素遍歷完成後,必須按照同樣的方法去將棧中剩餘元素全部退棧掉
void getNearNums(const vector<int>& nums, vector<pair<int, int>>& nearMinNums)
{
  stack<int> stIdx; // 單調棧 
  for (int i = 0; i < nums.size(); ++i) {
    if (stIdx.empty() || nums[i] > nums[stIdx.top()]) {
      stIdx.push(i); // 存下標
    } else {
      //棧不爲空,跟棧頂比較,只要這個棧頂還大於當前元素就要繼續退棧
      while (!stIdx.empty() && nums[i] < nums[stIdx.top()]) {
        int idx = stIdx.top();
        stIdx.pop();
        // first左小  second右小
        nearMinNums[idx].first = stIdx.empty() ? (-1) : (stIdx.top());
        nearMinNums[idx].second = i;
      }
      // 最後必須將當前元素給入棧
      stIdx.push(i);
    }
  }
  // stIdx還有元素
  while (!stIdx.empty()) {
    int idx = stIdx.top();
    stIdx.pop();
    nearMinNums[idx].first = stIdx.empty() ? (-1) : (stIdx.top());
    nearMinNums[idx].second = -1;
  }
}

有重複元素的單調棧

  • 基本的思路跟上面的單調棧還是相同的,不過有一些區別
  • 入棧時,棧中只有一個指針指向一個鏈表,鏈表中按照從頭到尾的順序在尾部插入相同值的元素的數組中的下標
  • 出棧時,左小且最近元素的下標爲新棧頂的鏈表的最右元素的下標 或 -1
    • 特別注意, 彈棧的時候彈棧的是整個鏈表,然後在整個鏈表上進行一個遍歷
    • 同樣的,彈棧的時刻就是當前被彈棧元素的計算時刻。

單調棧實戰

直方圖的最大矩形問題

題目連接
思路:
最大的那個面積肯定是取決於某個最低的高度值,必然是某個高度值。因此,我們只需要計算出每個高度能得到的最大面積,然後找到最大的面積就是最終答案。如何求每個矩形能達到的最大面積呢?利用遞歸棧可以分別找到左、右最小最近的元素的下標,因此相減不就是當前矩形的最大寬度嗎!

class Solution {
public:
    // 最大的那個面積肯定是取決於某個最低的高度值,必然是某個高度值,
    // 因此,我們只需要計算出每個高度能得到的最大面積,然後找到最大的面積就是最終答案
    unsigned long long largestRectangleArea(vector<int>& heights) {
        long maxArea = 0;
        stack<int> stIdx;
        for (int i = 0; i < heights.size(); ++i) {
          if (stIdx.empty() || heights[stIdx.top()] <= heights[i]) {
            stIdx.push(i);
          } else if (heights[stIdx.top()] > heights[i]) {
            while (!stIdx.empty() && heights[stIdx.top()] > heights[i]) {
              // 彈棧
              int index = stIdx.top();
              stIdx.pop();
              // 計算面積
              int height = heights[index];
              // 寬度就是最右邊的那個近小值-最左邊的近小值
              int width = (i) - (stIdx.empty() ? (-1) : (stIdx.top())) - 1;
              unsigned long long area = height * width;
              maxArea = area > maxArea ? area : maxArea;
            }
            stIdx.push(i);
          }
        }

        while (!stIdx.empty()) {
          int index = stIdx.top();
          stIdx.pop();
          // 計算面積
          int height = heights[index];
          // 寬度就是最右邊的那個近小值-最左邊的近小值
          int width = (heights.size()) - (stIdx.empty() ? (-1) : (stIdx.top())) - 1;
          long area = height * width;
          maxArea = area > maxArea ? area : maxArea;
        }
        return maxArea;
    }
};

leetcode反饋

參考資料

1 https://blog.csdn.net/weixin_40374341/article/details/100055210
2 https://blog.csdn.net/weixin_40374341/article/details/100065437

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