单调栈可以解决与数组中寻找下一个更大或者更小元素有关的问题,是栈的一个变种。
栈中元素是有序的,保持单调递增或单调递减。
栈(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
的 下一个更大元素 是指 x
在 nums2
中对应位置 右侧 的 第一个 比 x
大的元素。
给你两个 没有重复元素 的数组 nums1
和 nums2
,下标从 0 开始计数,其中nums1
是 nums2
的子集。
对于每个 0 <= i < nums1.length
,找出满足 nums1[i] == nums2[j]
的下标 j
,并且在 nums2
确定 nums2[j]
的 下一个更大元素 。如果不存在下一个更大元素,那么本次查询的答案是 -1
。
返回一个长度为 nums1.length
的数组 ans
作为答案,满足 ans[i]
是如上所述的 下一个更大元素 。
解题思路
可分为两个字问题:
- 算出下一个更大元素
用单调栈维护当前位置左边的最小元素,如果当前位置元素大于栈顶,则依次弹出栈中较小元素,弹出元素对应的下一个更大元素即当前元素。在最后将栈中剩余元素依次弹出,它们无下一个更大元素。 - 查询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
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
解题思路
- 使用动态规划
图片来自力扣官方题解
- 单调栈
维护一个单调栈,单调栈存储的是下标,满足从栈底到栈顶的下标对应的数组中的元素递减。
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);
}