目录
一、单调栈的定义
单调栈,顺序一致的栈,顾明思义就是单调递增或者单调递减的栈,那为什么要单独介绍这一数据结构呢?
因为单调栈的特性,使得它在面对“下一个元素”“不再升高减少”这类题目的时候,可以提供更优的算法解,时间复杂度也会降低。
二、单调栈的使用
单调栈,一般是需要对当前元素的左侧后右侧进行讨论
需要元素的左侧和右侧都小于当前元素时,使用单调递增栈
需要元素的左侧和右侧都大于当前元素时,使用单调递减栈
三、单调栈的LeetCode题目解析
1.每日温度 739
给定一个整数数组 ,表示每天的温度,返回一个数组 ,其中 是指对于第 天,下一个更高温度出现在几天后。 如果气温在这之后都不会升高,请在该位置用 来代替。temperatures
answer
answer[i]
i
0
示例 1:
输入: temperatures
= [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
stack<int> st;//单调栈保存数据
vector<int> result(temperatures.size(),0);//初始化皆为0
st.push(0);//默认加入下标为0,第一个元素
for(int i = 1;i<temperatures.size();i++){
if(temperatures[i] <= temperatures[st.top()]){
st.push(i);//将小于当前温度的都存入栈顶
}else{
//当前元素大于单调栈的栈顶
while(!st.empty() && temperatures[i] > temperatures[st.top()]){
result[st.top()] = i-st.top();//当前更高的温度,代表几天后升温
st.pop();//弹出
}
st.push(i);//然后将自己存入,此时要么比剩下的小,要么等于
}
}
return result;
}
};
题解:
本题要求几天后才会出现比当前温度高的,那么就可以用单调栈,将数组的小的元素一个个存入,形成一个单调递增的栈。遍历目标数组的每一个值,如果当前目标数组的值,比单调栈的栈顶元素等于或小于,就存入单调栈(这样就会形成单调递增)。如果大于单调栈的栈顶元素,那么就一个个比较赋值,设置result数组,将栈顶存储的索引下的值赋值为当前的索引减去栈中存储的索引的差,就是几天后。最后记得将元素存入栈,为了进行下一次比较。初始化的时候记得大小和目标一致,因为最终是要返回数组的
2.下一个更大元素 496
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]
是如上所述的 下一个更大元素 。
示例 1:
输入:nums1 = [4,1,2], nums2 = [1,3,4,2]. 输出:[-1,3,-1] 解释:nums1 中每个值的下一个更大元素如下所述: - 4 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。 - 1 ,用加粗斜体标识,nums2 = [1,3,4,2]。下一个更大元素是 3 。 - 2 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
stack<int> st;//单调栈
st.push(0);
vector<int> result(nums1.size(),-1);
unordered_map<int,int> save;
for(int i = 0 ;i <nums1.size();i++){
save[nums1[i]] = i;//建立映射关系
}
for(int i = 1;i<nums2.size();i++){
if(nums2[i]<= nums2[st.top()]){
st.push(i);//小于则存入-形成单调栈
}else{
while(!st.empty() && nums2[i] > nums2[st.top()]){
if(save.count(nums2[st.top()]) > 0){
//如果nums1含有该元素--更大元素
int indexNum1 = save[nums2[st.top()]];
result[indexNum1] = nums2[i];//返回下一个最大元素
}
st.pop();
}
st.push(i);
}
}
return result;
}
};
题解:
这题可以巧妙地使用单调栈解决。具体思路是,在遍历num2数组的时候,如果发现存在一个值大于单调栈的数组,那么这就是下一个更大元素,因为存在更大的数了。然后就可以在num1中寻找对应的索引,然后在结果数组中,赋值该元素到对应的索引下。寻找num1中的对应元素索引,可以利用哈希映射解决。这里采用性能较好的无序表进行保存索引和对应的值。那么在寻找的时候就可以利用count(值)来寻找到索引了。
3.下一个更大元素 2 503
给定一个循环数组 nums
( nums[nums.length - 1]
的下一个元素是 nums[0]
),返回 nums
中每个元素的 下一个更大元素 。
数字 x
的 下一个更大的元素 是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1
。
示例 1:
输入: nums = [1,2,1] 输出: [2,-1,2] 解释: 第一个 1 的下一个更大的数是 2; 数字 2 找不到下一个更大的数; 第二个 1 的下一个最大的数需要循环搜索,结果也是 2。
class Solution {
public:
vector<int> nextGreaterElements(vector<int>& nums) {
//扩展数组
vector<int> nums1(nums.begin(),nums.end());
nums.insert(nums.end(),nums1.begin(),nums1.end());//插入数组,从而扩充
vector<int> result(nums.size(),-1);
stack<int> st;
st.push(0);
for(int i = 1; i < nums.size();i++){
if(nums[i] <= nums[st.top()]){
st.push(i);//单调递增
}else{
while(!st.empty() && nums[i] > nums[st.top()]){
result[st.top()] = nums[i];
st.pop();//一个个向着更大的遍历
}
st.push(i);
}
}
result.resize(nums.size()/2);//切割数组
return result;
}
};
题解:
虽然要求是循环的数组,但是其实可以将数组扩充两倍,这样如果数组中存在下一个更大元素,就一定可以获得。逻辑上仍然保持,比栈顶小的元素进入栈,比栈顶大的,就进入循环,其本身就是栈内比该元素小的元素们的下一个更大元素,直到遇到更大的或者栈为空为止,才会停止循环,并且将当前的元素存入栈。在最后将结果数组还原成原来的大小(分割两半),然后就得到了解
4.接雨水 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 个单位的雨水(蓝色部分表示雨水)。
class Solution {
public:
int trap(vector<int>& height) {
if(height.size() <= 2){
return 0;//两个或以下数量的柱子构不成接雨水的装置
}
int num = 0;
stack<int> st;
st.push(0);
for(int i = 1; i<height.size();i++){
if(height[i] < height[st.top()]){
st.push(i);//形成单调递增的栈(从top开始向后)
}else if(height[i] == height[st.top()]){
//相同则不再存入,可以通过差值计算
st.pop();
st.push(i);//需要用它得到所有左侧
}else{
while(!st.empty() && height[i] > height[st.top()]){
int stop = st.top();//保存当前的top
st.pop();
if(!st.empty()){
//防止没有左侧
int h = min(height[i],height[st.top()]) - height[stop];//计算出积水的高度
int x = i - st.top() - 1;//右减去左
num += h*x;
}
}
st.push(i);
}
}
return num;
}
};
题解:
本题存储雨水,是需要一高一低一高的结构,而这恰恰就符合了单调栈的结构。所以在整体的处理思路上,可以使用单调栈,栈中元素越存越小,直到出现更大的元素时,就说明可以储存水了,这时候只需要依次去加上与前一个小元素可搭配储存的水分,直到高度和栈中剩余的元素一致为止,说明已经计算完了水分。其中,如果出现高度一致的情况,优先保留新的索引,去除旧的索引,因为当该索引被当做左侧高的时候,计算可搭配储存的水分,需要获得到最新的左侧,不然就会导致水分计算出错,水分就会计算多了。
5.柱状图中最大的矩形 84
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
示例 1:
输入:heights = [2,1,5,6,2,3] 输出:10 解释:最大的矩形为图中红色区域,面积为 10
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int sum = 0;//面积
heights.insert(heights.begin(),0);//为了实现一低一高一低的计算
heights.push_back(0);//扩充数组外部为0
stack<int> st;//单调递增栈
st.push(0);
for(int i = 1; i< heights.size();i++){
if(heights[i] > heights[st.top()]){
st.push(i);
}else if(heights[i] == heights[st.top()]){
st.pop();
st.push(i);//为了获取到最新的左侧
}else{
while(!st.empty() && heights[i] < heights[st.top()]){
int mid = st.top();
st.pop();
if(!st.empty()){
int left = st.top();
int right = i;
int h = heights[mid];//就是当前的高度
int x = right - left - 1;//总长度
sum = max(sum,h*x);
}
}
st.push(i);
}
}
return sum;
}
};
题解:
本题由于要计算最长的矩形,所以可以利用一低一高一低的形式去计算矩形面积。像这种当前元素的左侧右侧都要小于或者大于的情况,就可以使用单调栈。是需要改一下存储条件即可。本题就是存储越来越大的元素,直到出现一个小元素,就是低,然后就遍历栈,不断获取到最大的面积,直到没有比当前小元素更小的栈顶元素为止,这样就可以通过不断对当前栈顶元素的高和不断增长的底进行相乘,在比较中最终获得一个最最大的可勾勒的矩形。当然为了问题的有解,需要考虑到万一提供的数组全为递增或者递减的情况,就需要有0去约束,全为递增的话,末尾为0,这样就可以向前遍历单调栈,全为递减的话,首部为0,这样就可以正常进行计算,而不忽略掉一开始的解(否则就会因为empty而否定执行掉计算大小的步骤)。