单调栈

文章介绍了单调栈这种数据结构在解决数组中寻找下一个更大或更小元素问题上的应用,包括每日温度、下一个更大元素I和接雨水等经典问题的解题思路和AC代码。单调栈保持元素的单调递增或递减,通过比较新元素和栈顶元素来维护栈的单调性,从而高效地找到目标元素。
摘要由CSDN通过智能技术生成

单调栈可以解决与数组中寻找下一个更大或者更小元素有关的问题,是的一个变种。
栈中元素是有序的,保持单调递增或单调递减。
(Stack)是一种操作受限的线性表,只允许一端进,同一端出,因而具有后进先出(LIFO)的特性。

之前在知乎上看到某dalao的描述(找不到了;(
如是说: 如果新来的oier比你年轻还比你厉害,那么你就该退役了。

维护:每次入栈需要用插入元素与栈顶元素进行比较,如果插入不符合单调性,则需要将栈中不符合的元素全部弹出,再插入元素。

时间复杂度和空间复杂度

  • 时间复杂度: O(n)

核心代码

/** 栈顶最小的单调栈stk,栈中存q数组下标i */
for (int i = 0; i < n; ++i) {
  while (i < n && !stk.empty() && q.at(i) >= q.at(stk.top)) stk.pop();
  stk.push(i);
}

练习题目

1.每日温度

题目描述

给定一个整数数组表示每天的温度, 计算出对于每一天来说,更高的温度在日后第几天出现。

解题思路

维护一个储存下标的单调栈,从栈低到栈顶的下标对应的温度依次渐小,如果一个下标在单调栈里则表示改下表天数还未有比之温度更高的温度出现。

AC代码
/**
 * \link: https://leetcode.cn/problems/daily-temperatures/ 
 * \category: 单调栈 array
 * 
 * 
 *
 **/
#include <algorithm>
#include <iostream>
#include <stack>
#include <vector>
#include <unordered_map>
using namespace std;

/* using 单调栈 postorder traveral. */
class Solution {
 public:
  vector<int> dailyTemperatures(vector<int> &temperatures) {
    /** init. */
    stack<int> stk;
    int n = temperatures.size();
    vector<int> ans(n);

    /** postorder traveral. */
    for (int i = n - 1; ~i; --i) {
      int num = temperatures.at(i);
      while (!stk.empty() && num >= temperatures[stk.top()]) stk.pop();
      ans[i] = stk.empty() ? 0 : stk.top() - i;
      stk.push(i);
    }
    
    return ans;
  }
};

/* using 单调栈 preorder traveral. */
class Solution {
 public:
  vector<int> dailyTemperatures(vector<int>& temperatures) {
    /** init. */
    stack<int> stk;
    int n = temperatures.size();
    vector<int> ans(n);

    /** preorder traveral. */
    for (int i = 0; i < n; ++i) {
      while (!stk.empty() && temperatures.at(i) > temperatures.at(stk.top())) {
        ans.at(stk.top()) = i - stk.top();
        stk.pop();
      }
      stk.push(i);
    }
    return ans;
  }
};

2.下一个更大元素 I

题目描述

nums1 中数字 x下一个更大元素 是指 xnums2 中对应位置 右侧第一个x 大的元素。
给你两个 没有重复元素 的数组 nums1nums2 ,下标从 0 开始计数,其中nums1nums2 的子集。
对于每个 0 <= i < nums1.length ,找出满足 nums1[i] == nums2[j] 的下标 j ,并且在 nums2 确定 nums2[j]下一个更大元素 。如果不存在下一个更大元素,那么本次查询的答案是 -1
返回一个长度为 nums1.length 的数组 ans 作为答案,满足 ans[i] 是如上所述的 下一个更大元素

解题思路

可分为两个字问题:

  1. 算出下一个更大元素
    用单调栈维护当前位置左边的最小元素,如果当前位置元素大于栈顶,则依次弹出栈中较小元素,弹出元素对应的下一个更大元素即当前元素。在最后将栈中剩余元素依次弹出,它们无下一个更大元素。
  2. 查询num1中元素的下一个更大元素
    用unorderd_map做哈希表储存对应关系。
AC代码
/**
 * \link: https://leetcode.cn/problems/next-greater-element-i/
 * \category: 栈 数组 哈希表 单调栈
 * 
 * 
 * 
 **/
#include <algorithm>
#include <iostream>
#include <sstream>
#include <stack>
#include <unordered_map>
#include <vector>
using namespace std;

/* using monotonic stack + preorder traversal. */
class Solution {
 public:
  vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
    /** init */
    stack<int> stk;
    int l2 = nums2.size(), l1 = nums1.size();
    unordered_map<int, int> hashmap;

    /** preorder traversal. */
    for (int i = 0; i < l2; ++i) {
      while (!stk.empty() && nums2.at(i) > stk.top()) {
        hashmap[stk.top()] = nums2.at(i);
        stk.pop();
      }
      stk.push(nums2.at(i));
    }
    while (!stk.empty()) {
      hashmap[stk.top()] = -1;
      stk.pop();
    }

    /** search the ans of nums1. */
    vector<int> ans(nums1);
    for (int i = 0; i < l1; ++i) {
      int num = nums1.at(i);
      if (hashmap.count(num)) ans[i] = hashmap[num];
    }
    return ans;
  }
};

/** using monotonic stack + postorder traversal. */
class Solution {
 public:
  vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
    stack<int> stk;
    unordered_map<int, int> hashmap;
    /** postorder traversal. */
    for (int i = nums2.size() - 1; ~i; --i) {
      int num = nums2.at(i);
      while (!stk.empty() && num >= stk.top()) stk.pop();
      hashmap[num] = (stk.empty() ? -1 : stk.top()); 
      stk.push(num);
    }

    vector<int> ans(nums1.size());
    for (int i = 0; i < nums1.size(); ++i) {
      ans[i] = hashmap[nums1[i]];
    } 
    return ans;
  }
};

3.接雨水

题目描述

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

解题思路
  1. 使用动态规划
    在这里插入图片描述

图片来自力扣官方题解

  1. 单调栈
    维护一个单调栈,单调栈存储的是下标,满足从栈底到栈顶的下标对应的数组中的元素递减。
AC代码
/**
 * \link: https://leetcode.cn/problems/trapping-rain-water/description/
 * \category: 双指针 stak array two pointers 
 *
 *
 *
 **/
#include <algorithm>
#include <iostream>
#include <sstream>
#include <stack>
#include <unordered_map>
#include <vector>

using namespace std;

/** using 动态规划
 * For indix i, the maximum height that water can reach afer rain is equal to
 * the lower profile of the maximum height on both sides of indix i. And the
 * amount of the rain can be receiced at indix i is equal to the maximum height
 * at indix i minus the height at indix i. */
class Solution {
 public:
  int trap(vector<int>& height) {
    int n = height.size();
    if (n == 0) return 0;
    /** left max */
    vector<int> left_max(height);
    int maxx = 0;
    for (int i = 0; i < n; ++i) {
      maxx = max(maxx, height[i]);
      left_max[i] = maxx;
    }

    /** right max */
    vector<int> right_max(height);
    maxx = 0;
    for (int i = n - 1; ~i; --i) {
      maxx = max(maxx, height[i]);
      right_max[i] = maxx;
    }

    /** get ans */
    int ans = 0;
    for (int i = 0; i < n; ++i) {
      ans += min(left_max[i], right_max[i]) - height[i];
    }

    return ans;
  }
};

/** using monotonic stack. */
class Solution {
 public:
  int trap(vector<int>& height) {
    int n = height.size();
    int ans = 0;
    stack<int> stk;
    for (int i = 0; i < n; ++i) {
      while (!stk.empty() && height.at(i) > height.at(stk.top())) {
        int top = stk.top();
        stk.pop();
        if (stk.empty()) break;
        int l = stk.top();
        int width_now = i - 1 - l;
        int height_now = min(height.at(i), height.at(l)) - height.at(top);
        ans += height_now * width_now;
      }
      stk.push(i);
    }
    return ans;
  }
};

4.直方图中的最大矩形

题目描述

直方图是由在公共基线处对齐的一系列矩形组成的多边形。矩形具有相等的宽度,但可以具有不同的高度。计算在公共基线处对齐的直方图中最大矩形的面积。
请添加图片描述

图片来自AcWing

解题思路

从所求下标开始向左右两边扩展,若高度比自身大则继续扩展,只到遇到更小的为止。
设下标 0 和 n + 1为边界, 故 S = (r - l - 1) * h
考虑单调栈优化。

AC代码
/**
  * \link: https://www.acwing.com/problem/content/133/
  * \category: 单调栈 
  * 
  *
  * \note: 
  **/
#include <algorithm>
#include <array>
#include <iostream>
#include <stack>

// #define ONLINE_JUDGE 
using namespace std;
using ll = long long;

const int kN = 1e5 + 233;

array<int, kN> h, l, r;
stack<int> stk;

inline void solve() {
  int n; while (cin >> n , n) {
    for (int i = 1; i <= n; ++i) scanf("%d", &h[i]);
    /** define the bound  */
    h[0] = h[n + 1] = -1;
    stk.push(0);
    
    /** 用单调栈找当前位置的左边界线 */
    for (int i = 1; i <= n; ++i) {
      while (h[i] <= h[stk.top()]) stk.pop();
      l[i] = stk.top();
      stk.push(i);
    }

    /** define the bound to next */
    while (!stk.empty()) stk.pop();
    stk.push(n + 1);
    
    /** 用单调栈找当前位置的右边界线 */
    for (int i = n; i; --i) {
      while (h[i] <= h[stk.top()]) stk.pop();
      r[i] = stk.top();
      stk.push(i);
    }

    ll ans = 0;
    for (int i = 1; i <= n; ++i) ans = max(ans,(ll) (r[i] - l[i] - 1) * h[i]);
    cout << ans << '\n';
  }
  return ;
}

bool rt = false;
 int main() {
  // ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
  #ifndef ONLINE_JUDGE
  freopen("test.in", "r", stdin);
  #endif
  if (rt) { int T; cin >> T; while(T--) solve(); }
  else solve();
  return (0 ^ 0);
 }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值