今天写个短的笔记来专门记录一下有关代码随想录中单调栈的题目:
单调栈的作用其实非常的简单易懂:他就是用来寻找每个数组内元素往右第一个比他大的元素。(或者小的,或者往左,方向和大小不卡死)如果单调栈是递增的,那么求的就是比他大的元素,我们这样想更直观:加入的元素比栈顶的元素大的话,说明我们找到了比之前遍历的元素更大的元素了,反之亦然。
给定一个整数数组 temperatures
,表示每天的温度,返回一个数组 answer
,其中 answer[i]
是指对于第 i
天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0
来代替。
cpp
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
vector<int> res(temperatures.size());
stack<int> stk;
for(int i=0;i<temperatures.size();++i){
while(!stk.empty()&&temperatures[i]>temperatures[stk.top()]){
int pre=stk.top();
stk.pop();
res[pre]=i-pre;
}
stk.push(i);
}
return res;
}
};
一个很简单的单调栈的题目,我们用栈来记录已经遍历过的序号并且向右寻找第一个比当前元素更大的元素,得到之后就将结果放在res数组里。
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]
是如上所述的 下一个更大元素 。
cpp
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
vector<int> res;
stack<int> stk;
unordered_map<int,int> mp;
for(int num:nums2){
while(!stk.empty()&&num>stk.top()){
mp[stk.top()]=num;
stk.pop();
}
stk.push(num);
}
for(int& num:nums1){
num=mp.count(num)?mp[num]:-1;
}
return nums1;
}
};
这个题比起上一个题来说要绕得多,但是我们可以利用哈希表的性质(count,contains等)来先在nums2中执行找到右边第一个比当前大的值,然后反过来去nums1寻找重复的值。
503. 下一个更大元素 II - 力扣(LeetCode)
给定一个循环数组 nums
( nums[nums.length - 1]
的下一个元素是 nums[0]
),返回 nums
中每个元素的 下一个更大元素 。
数字 x
的 下一个更大的元素 是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1
。
cpp
class Solution {
public:
vector<int> nextGreaterElements(vector<int>& nums) {
int n=nums.size();
vector<int> res(n,-1);
stack<int> stk;
for(int i=0;i<2*n;++i){
while(!stk.empty()&&nums[i%n]>nums[stk.top()]){
res[stk.top()]=nums[i%n];
stk.pop();
}
stk.push(i%n);
}
return res;
}
};
所谓的循环数组只需要我们去寻找两遍即可,我们主要需要做的是注意序号的取余操作。
给定 n
个非负整数表示每个宽度为 1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
cpp
class Solution {
public:
int trap(vector<int>& height) {
int res=0,left=0,right=height.size()-1;
int leftmax=height[0],rightmax=height.back();
while(left<right){
if(height[left]<height[right]){
res+=leftmax-height[left];
left++;
leftmax=max(leftmax,height[left]);
}
else{
res+=rightmax-height[right];
right--;
rightmax=max(rightmax,height[right]);
}
}
return res;
}
};
这是双指针做法,我们维护左右指针以及左右最大值,慢慢推进左右指针并将每一步移动的值相加即可。
cpp
class Solution {
public:
int trap(vector<int>& height) {
int res=0;
stack<int> stk;
for(int i=0;i<height.size();++i){
while(!stk.empty()&&height[i]>height[stk.top()]){
int mid=stk.top();
stk.pop();
if(!stk.empty()){
int h=min(height[stk.top()],height[i])-height[mid];
int w=i-stk.top()-1;
res+=h*w;
}
}
stk.push(i);
}
return res;
}
};
这个是单调栈做法,其实本质上也是寻找向右的第一个比当前值大的值,这里巧妙的利用了单调栈的另一个性质:栈顶元素的栈内下一个元素一定也是比当前大的,也就是也是当前元素向左的第一个比当前值大的元素(如果不是的话就会触发while的条件,被弹出栈外),所以我们可以同时得到当前元素向左和向右的第一个比当前元素大的元素,我们就将结果相加即可。
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
cpp
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int res=0;
heights.insert(heights.begin(),0);
heights.push_back(0);
stack<int> stk;
stk.push(0);
for(int i=1;i<heights.size();++i){
while(heights[stk.top()]>heights[i]){
int mid=stk.top();
stk.pop();
int h=heights[mid];
int w=i-stk.top()-1;
res=max(res,h*w);
}
stk.push(i);
}
return res;
}
};
这个题需要注意的点是我们的边界初始化:我们这题需求的是找寻当前值两遍的第一个比当前值小的值,那如果我们从局部最小值开始可能会出现完全不触发while的条件,于是为了保证至少进行一次面积的计算,我们在最后和最开始的数组处都添加一个0,而stack处添加0则是为了对应一开始添加的0序号上的对应(不然会对不上序号)。
单调栈的题都有着比较明显的用意:找寻每个元素的往左、往右的第一个比当前值大、小的值,而且思路总的来说比较模板,还是比较简单的。