第一部分:简单介绍
单调栈我的理解是在栈中存储数字出现的位置,然后通过遍历比较当前栈顶元素与当前元素的大小关系,从而确定逻辑相关顺序。
第二部分:真题讲解
(1)739. 每日温度 - 力扣(LeetCode)
解题思路:在栈中存储每日的下标。如果当前元素大于栈顶元素,那么符合题意,就需要接下来的弹出以及赋值操作。如果小于等于则进行压栈。
cpp
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
stack<int>stack;
int n=temperatures.size();
vector<int>ans(n,0);
stack.push(0);
for(int i=1;i<temperatures.size();i++){
while(stack.empty()==0&&temperatures[i]>temperatures[stack.top()]){
int a=stack.top();
stack.pop();
ans[a]=i-a;
}
stack.push(i);
}
return ans;
}
};
(2)496. 下一个更大元素 I - 力扣(LeetCode)
解题思路:首先对数组2进行单调栈构建,然后将其存储到map中,避免两次遍历n^2的时间复杂度。然后通过map直接寻找即可。
cpp
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
vector<int>dict(nums2.size(),-1);
vector<int>ans;
stack<int>stack;
for(int i=0;i<nums2.size();i++){
while(stack.empty()==0&&nums2[i]>nums2[stack.top()]){
int a=stack.top();
stack.pop();
dict[a]=nums2[i];
}
stack.push(i);
}
unordered_map<int,int>cnt;
for(int i=0;i<nums2.size();i++){
cnt[nums2[i]]=dict[i];
}
for(int i=0;i<nums1.size();i++){
ans.push_back(cnt[nums1[i]]);
}
return ans;
}
};
(3)503. 下一个更大元素 II - 力扣(LeetCode)
本题思路:与上一题类似,但是不同的是必须有一个循环。那么循环需要考虑模运算。这里只需要加一个入栈的条件即可,即stack.empty()||(i%n)!=stack.top()
cpp
class Solution {
public:
vector<int> nextGreaterElements(vector<int>& nums) {
//1.准备
vector<int>dict(nums.size(),-1);
stack<int>stack;
int n=nums.size();
//2.开始构建单调栈
for(int i=0;i<nums.size()*2;i++){
while(stack.empty()==0&&nums[i%n]>nums[stack.top()]){
int a=stack.top();
stack.pop();
dict[a]=nums[i%n];
}
if(stack.empty()||(i%n)!=stack.top()){
stack.push(i%n);
}
}
//3.output
return dict;
}
};
(4)42. 接雨水 - 力扣(LeetCode)
思路一:使用双指针法,按列相加,分别向左向右遍历,然后每一个位置的雨水=min(左,右)-height,然后累加就可以。
cpp
class Solution {
public:
int trap(vector<int>& height) {
//1.initial
vector<int>leftnum(height.size(),0);
vector<int>rightnum(height.size(),0);
int n=height.size();
//2.look for
leftnum[0]=height[0];
for(int i=1;i<height.size();i++){
leftnum[i]=max(leftnum[i-1],height[i]);
}
rightnum[n-1]=height[n-1];
for(int i=n-2;i>=0;i--){
rightnum[i]=max(rightnum[i+1],height[i]);
}
//3.sum
int sum=0;
for(int i=0;i<height.size();i++){
sum+=min(leftnum[i],rightnum[i])-height[i];
}
return sum;
}
};
思路二:单调栈(按行相加)
这个思路比较巧妙,如果成功构造了一个单调栈的话,那么就存在一种情况,b<a且b<c,其中b是栈顶元素,a是当前遍历到的数值,c是栈顶元素的下一个。那么就构成了接雨水的水槽。那么这个可以构成循环一直循环下去,所以我们只需要不断构建这个水槽就可以。
cpp
class Solution {
public:
int trap(vector<int>& height) {
int n=height.size();
vector<int>ans(n,0);
stack<int>stack;
int sum=0;
//按行相加
for(int i=0;i<n;i++){
while(stack.empty()==0&&height[i]>height[stack.top()]){
int a=stack.top();
stack.pop();
if(!stack.empty()){
int b=stack.top();
int alt=min(height[i],height[b])-height[a];
int width=i-b-1;
sum+=alt*width;
}
}
stack.push(i);
}
return sum;
}
};
(5)84. 柱状图中最大的矩形 - 力扣(LeetCode)
解题思路:
- 首先明确最大矩形怎么找:肯定是通过迭代一步一步更新来的。
- 那么怎么更新呢,就是通过先找到一个值,这个值右面的所有柱都比他大,也就是说直接用他的高去乘宽就是最大矩形。
- 如何用到单调栈呢,那就是说单调递减栈,如果当前元素比栈顶元素小则求矩形,否则(大于等于)则入栈。
- 还要在首尾加两个0,在尾节点加0是因为防止2468这种情况的存在。在头节点加是为了防止8642这种情况出现,无法进行计算,只是一味的弹出。
cpp
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
stack<int>stack;
int acre=0;
//1.首先处理的时候要首尾加0
heights.insert(heights.begin(),0);
heights.push_back(0);
//2.单调栈的构建
for(int i=0;i<heights.size();i++){
while(!stack.empty()&&heights[i]<heights[stack.top()]){
//单调递减栈
int mid=stack.top();
stack.pop();
if(!stack.empty()){
int left=stack.top();
int width=i-left-1;
int gaodu=heights[mid];
acre=max(acre,width*gaodu);
}
}
stack.push(i);
}
//3.输出
return acre;
}
};