定义
栈内元素按照递增或者递减的顺序排列的栈
适用问题
单调栈分为单调递增与单调递减栈,可以用于获取下一个比当前元素大(小)的元素。当需要通过比较前后元素的大小关系时,可以使用单调栈解决问题。
单调递减栈
- 在队列中针对每个元素从右边寻找第一个比它大的元素
- 在队列中针对每个元素从左边寻找第一个比它大的元素(从后往前)
这两种解决问题过程一样,但是角度不同。
使用下面这个例子来解释:
有一本武功秘籍,根据先来后到的顺序教授人武功。但是在排队过程中,如果有武功更加高强的人发现前面的人武功不如自己,就会打发他离开,并把自己的武功传授给他(这一过程就相当于为前面的人找到右侧第一个大于它的元素)。然后如果发现前面不如自己武功的人都被打发走了,此时站在面前的是武功高于自己的人(相当于找到为自己找到左侧第一个大于自己的元素), 就停下脚步。
伪代码如下:
对于第i个到来的人:
每当栈中有人并且打不过自己的时候:
让这个人离开,并且将这个人右侧的第一个能打过他的记录为第i个人
// 也可以记录栈顶的人为离开的人的左侧第一个不能打过的人
记录第i个人左侧第一个不能打过的人为栈顶的人
自己入队
下面看几道使用单调栈解决的问题:
901 股票价格跨度
编写一个 StockSpanner 类,它收集某些股票的每日报价,并返回该股票当日价格的跨度。
今天股票价格的跨度被定义为股票价格小于或等于今天价格的最大连续日数(从今天开始往回数,包括今天)。
例如,如果未来7天股票的价格是 [100, 80, 60, 70, 60, 75, 85],那么股票跨度将是 [1, 1, 1, 2, 1, 4, 6]。
问题分析: 这道题目需要求解的是小于或等于今天价格的最大连续日数,其实就是求解左侧大于今天价格的那一天。符合单调递减栈的特性。
代码:
class StockSpanner {
public:
int index;
stack<pair<int,int>> s;
StockSpanner() {
index = 0;
}
int next(int price) {
index++;
while(!s.empty() && s.top().second<=price)
{
s.pop();
}
int ans;
if(s.empty()) // 当栈为空的时候表示前面的元素都小于当前元素,故ans = index
ans = index;
else {
ans = index-s.top().first;
}
s.push({
index,price});
return ans;
}
};
1019 链表中的下一个更大节点
给出一个以头节点 head 作为第一个节点的链表。链表中的节点分别编号为:node_1, node_2, node_3, … 。
每个节点都可能有下一个更大值(next larger value):对于 node_i,如果其 next_larger(node_i) 是 node_j.val,那么就有 j > i 且 node_j.val > node_i.val,而 j 是可能的选项中最小的那个。如果不存在这样的 j,那么下一个更大值为 0 。
返回整数答案数组 answer,其中 answer[i] = next_larger(node_{i+1}) 。
注意:在下面的示例中,诸如 [2,1,5] 这样的输入(不是输出)是链表的序列化表示,其头节点的值为 2,第二个节点值为 1,第三个节点值为 5 。
问题分析: 本质上也是求右侧第一个大于当前元素的元素。
代码:
vector<int> nextLargerNodes(ListNode* head) {
stack<pair<int,int>> s;
vector<int> res;
int index = 0;
while(head)
{
res.push_back(0);// 这里是为了保证链表每个元素都有对应结果 方便之后按照下标进行修改 而且最后栈中剩下的元素是按照从大到小排序的,无右侧更大值 res = 0
while(!s.empty() && s.top().second < head->val)
{
res[s.top().first] = head->val;
s.pop();
}
s.p