单调栈简介
什么时候用单调栈
当题目给出一维数组,且要求我们求出数组元素的左边或者右边第一个比自己大或者比自己小的数组元素的下标或者数组元素本身的时候。注意,这里就有四种情况:左边比自己大,左边比自己小,右边比自己大,右边比自己小。
单调栈的原理
- 单调栈顾名思义是单调递增的栈,递增方向是从栈顶到栈底,也就是说栈顶元素小,栈底元素大。 这种情况就是求一个元素右边第一个更大元素。
- 反之,要求一个元素右边第一个更小元素,单调栈是单调递减的栈,递增方向是从栈顶到栈底,也就是说栈顶元素大,栈底元素小。
另外,我们在单调栈里存储元素下标即可。那么元素是怎么进出单调栈的?总的原则是保持单调栈从栈顶至栈底单调递增。这里我们以求一个元素右边第一个更大元素为例,首先将第一个元素的下标入栈,接下来遍历数组,分为三种情况:
- 当前遍历的元素V[i]小于栈顶元素V[st.top()],i加入单调栈;
- 当前遍历的元素V[i]等于栈顶元素V[st.top()],i加入单调栈;
- 当前遍历的元素V[i]大于栈顶元素V[st.top()],单调栈的元素出栈,直至碰到比当前元素小于等于的元素,每次st.pop()之前都要记录结果res[st.top()]=i-st.top(),res[st.top()]=i-st.top(),res[st.top()]=i-st.top(),res[st.top()]=i-st.top(),res[st.top()]=i-st.top();最后i加入单调栈。
例题解析
每日温度
给定一个整数数组 temperatures
,表示每天的温度,返回一个数组 answer
,其中 answer[i]
是指对于第 i
天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0
来代替。
解析:题目要求比当天气温更高的一天--右边比自己大,则使用单调栈!
cpp
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
auto sz=temperatures.size();
vector<int> res(sz,0);
stack<int> st;
st.push(0);
for(int i=1;i<sz;i++)
{
if(temperatures[i]<temperatures[st.top()])
{
st.push(i);
}
else if(temperatures[i]==temperatures[st.top()])
{
st.push(i);
}
else
{
while(!st.empty()&&temperatures[st.top()]<temperatures[i])
{
res[st.top()]=i-st.top();
st.pop();
}
st.push(i);
}
}
return res;
}
};
时间复杂度和空间复杂度都是O(N)。
下一个更大元素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]
是如上所述的 下一个更大元素 。
解析:nums1是nums2的子集,且顺序是打乱的。我们要先知道nums1元素在nums2元素的位置,因此用unordered_map进行映射即可。接下来遍历nums2,如果栈中的元素存在于nums1,则记录结果。
cpp
if(hash.count(nums2[st.top()])>0)// 栈中的元素在nums1中
{
int index=hash[nums2[st.top()]];
res[index]=nums2[i];// 当前遍历的元素
}
cpp
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
int sz1=nums1.size(),sz2=nums2.size();
unordered_map<int,int> hash;
stack<int> st;
st.push(0);
vector<int> res(sz1,-1);
for(int i=0;i<sz1;i++)
{
hash[nums1[i]]=i;
}
for(int i=1;i<sz2;i++)
{
if(nums2[i]<nums2[st.top()])
{
st.push(i);
}
else if(nums2[i]==nums2[st.top()])
{
st.push(i);
}
else
{
while(!st.empty()&&nums2[i]>nums2[st.top()])
{
if(hash.count(nums2[st.top()])>0)// 栈中的元素在nums1中
{
int index=hash[nums2[st.top()]];
res[index]=nums2[i];// 当前遍历的元素
}
st.pop();
}
st.push(i);
}
}
return res;
}
};
下一个更大的元素II
给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。
解析:本题较上一题,思路一样,处理比较简单。碰到循环,基本思路就是%size。本题还需注意的是要遍历到size*2,才能达到循环的效果。
cpp
class Solution {
public:
vector<int> nextGreaterElements(vector<int>& nums) {
int sz=nums.size();
vector<int> res(sz,-1);
stack<int> st;
st.push(0);
for(int i=1;i<sz*2;i++)
{
if(nums[i%sz]<nums[st.top()])
{
st.push(i%sz);
}
else if(nums[i%sz]==nums[st.top()])
{
st.push(i%sz);
}
else
{
while(!st.empty()&&nums[i%sz]>nums[st.top()])
{
res[st.top()]=nums[i%sz];
st.pop();
}
st.push(i%sz);
}
}
return res;
}
};
接雨水
给定 n
个非负整数表示每个宽度为 1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
解析:本题个人觉得双指针思路较简单。只需找到当前位置左边和右边的最大高度,取二者的最小值-当前位置高度即可。注意0和size-1的位置无需处理。
cpp
class Solution {
public:
int trap(vector<int>& height) {
int res=0;
int sz=height.size();
for(int i=1;i<sz-1;i++)
{
int h=height[i];
int l=h,r=h;
for(int j=i-1;j>=0;j--)
{
if(height[j]>l)
l=height[j];
}
for(int j=i+1;j<sz;j++)
{
if(height[j]>r)
r=height[j];
}
int ans=min(l,r)-h;
if(ans>0) res+=ans;
}
return res;
}
};
但时间复杂度为O(n^2),测试用例的最后一个超时了。
或者我们运用动态规划的思想提前把当前高度左边和右边最高求一下,用空间换时间。
cpp
class Solution {
public:
int trap(vector<int>& height) {
int sz=height.size();
vector<int> maxL(sz);// 每个柱子左边最大的
vector<int> maxR(sz);// 每个柱子右边最大的
int res=0;
// 初始化
maxL[0]=height[0];
for(int i=1;i<sz;i++)
{
maxL[i]=max(maxL[i-1],height[i]);
}
// 初始化
maxR[sz-1]=height[sz-1];
for(int i=sz-2;i>=0;i--)
{
maxR[i]=max(height[i],maxR[i+1]);
}
for(int i=1;i<sz-1;i++)
{
int tmp=min(maxL[i],maxR[i])-height[i];
if(tmp>0)
res+=tmp;
}
return res;
}
};