栈 stack,这种数据结构的特性是 先进后出。 但是 进栈 和 出栈 的顺序可以穿插进行,这样就使栈这种数据结构能够处理复杂的问题。
单调栈 就是一种 对栈 通过 加入条件变量控制 ,通过 控制 进栈和出栈 的操作,实现栈中存储的数据 是单调的, 即压入栈中的数据是 单调增 或 单调减的。
可以用来处理 下一个更大元素的问题, 接雨水问题。
第一题:Leetcode 496
题目描述:
给你两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。
请你找出 nums1 中每个元素 在 nums2 中的下一个比其大的值。
nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1 。
例:
输入: nums1 = [4,1,2], nums2 = [1,3,4,2]. 输出: [-1,3,-1]
对于题目的解读:
关键信息是: 找出 nums1 中每个元素 在 nums2 中的下一个比其大的值。
对于使用暴力解法很好解决,只需要针对nums1中的每个变量,在nums2中进行遍历,找出在nums2中右边第一个更大的值。 但是时间复杂度很高,时间复杂度是O(n^2).
对于下一个更大元素问题, 可以使用单调栈, 对数组从后往前遍历, 在单调栈中存储 右边第一个更大的元素值 。
处理的思路:
1.如何处理 nums2中的每个元素的 右边第一个更大的元素
2.如何 存储 nums2中 处理 的结果, 方便 查找nums1中的元素,在nums2中的右边第一个更大的元素
首先, 对 nums2的所有元素都 求解 右边的第一个更大的元素 ;然后,将结果存 在一个哈希表中 , 通过 nums1的值来 获取相应的哈希值 ,即 右边第一个更大的元素。
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
unordered_map<int,int> hashmap;
stack<int> s;
vector<int> res(nums1.size(),0);
//求出 nums2中的每个元素的第一个右边的最大值
for(int n = nums2.size()-1; n>=0 ;n--)
{
while(!s.empty() && s.top() <= nums2[n])
{
s.pop();
}
hashmap[nums2[n]] = s.empty()? -1: s.top();
s.push(nums2[n]);
}
//在存的结果中,查找 nums1中 每个元素在 nums2中 右边第一个更大值
for(int i=0; i<nums1.size() ;i++)
{
res[i] = hashmap[nums1[i]];
}
return res;
}
};
在上面的函数中, 假如输入数据是 nums2 = [1,3,4,2] ,处理完nums2后,栈 s内 还有两个元素1、3 和 4。 这样 处理 nums2数组 ,从始至终, 整个栈s 都是一个单调栈。 这就是单调栈的含义。
第二题: Leetcode 42
题目描述:
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例 1:
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
示例 2:
输入:height = [4,2,0,3,2,5]
输出:9
解决的思路:
看到这个题目,就觉得属于,下一个更大的元素类型 的问题。 但是还是不一样的,找到更高的柱子后,处理 这个更高的柱子 和 前高柱子 之间的数据。 这个更高的柱子,是针对不断更新的 top 的元素而言的。
因为柱子的高度是不确定,能存水的情况有两种: 最高柱子 左高右低的, 也有最高柱子 左低右高的。
维护一个单调栈,单调栈存储的是下标,满足从栈底到栈顶的下标对应的数组 height 中的元素递减。
从左到右遍历数组,遍历到下标 i时,如果栈内至少有两个元素,记栈顶元素为 top,top 的下面一个元素是 left,则一定有 height[left]≥height[top]。如果 新的遍历的柱子高度 height[i]>height[left],则得到一个可以接雨水的区域,该区域的宽度是 i−left−1,高度是 min(height[left],height[i])−height[top],根据宽度和高度相乘,即可计算得到该区域能接的雨水量。
为了得到 left,需要将 top 出栈。在对 top 计算能接的雨水量之后,left 变成新的 top,重复上述操作,直到栈变为空,或者栈顶下标对应的 height 中的元素大于或等于 height[i]。
通过一个 while循环 来对可以 存储水的区间 进行计算, 在循环内是通过不断地变换 top 和 left,从而实现了统计雨水量。
在对下标 i 处计算能接的雨水量之后,将 i入栈,继续遍历后面的下标,计算能接的雨水量。遍历结束之后即可得到能接的雨水总量。
比如:上图中的 区域1 和 区域2 ,就是经过计算的 可以存雨水的区域。
代码如下:
class Solution {
public:
int trap(vector<int>& height) {
int size = height.size();
int water = 0;
stack<int> s;
for(int i =0 ;i<size ; ++i )
{
//找到 第一个右边 更高的柱子
while(!s.empty() && height[s.top()] <= height[i])
{
int top = s.top();
s.pop();
if(s.empty()) break;
int left = s.top();
int width = i - left -1;
int heigh = min(height[i],height[left]) - height[top];
water += width*heigh;
}
s.push(i); // 栈中存的是坐标
}
return water;
}
};
对于接雨水的问题,还有其他的解法,大家可以去看看相关的题解。
关于单调栈,当有更多的收获的时候,会继续更新.............
放一张风景照吧,缓解一下疲劳。 (接雨水,想了两次,还是看题解才明白 QAQ)