算法-单调栈

算法题-单调栈相关

单调栈

  
  通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了。时间复杂度为O(n)。

  单调栈的本质是空间换时间,因为在遍历的过程中需要用一个栈来记录右边第一个比当前元素高的元素,优点是整个数组只需要遍历一次。

  更直白来说,就是用一个栈来记录我们遍历过的元素,因为我们遍历数组的时候,我们不知道之前都遍历了哪些元素,以至于遍历一个元素找不到是不是之前遍历过一个更小的,所以我们需要用一个容器(这里用单调栈)来记录我们遍历过的元素。

  在使用单调栈的时候首先要明确如下几点:

  1. 单调栈里存放的元素是什么?

  单调栈里只需要存放元素的下标i就可以了,如果需要使用对应的元素,直接T[i]就可以获取。

  1. 单调栈里元素是递增呢? 还是递减呢?

  注意以下讲解中,顺序的描述为 从栈头到栈底的顺序,因为单纯的说从左到右或者从前到后,不说栈头朝哪个方向的话,大家一定比较懵。

  这里我们要使用递增循序(再强调一下是指从栈头到栈底的顺序),因为只有递增的时候,栈里要加入一个元素i的时候,才知道栈顶元素在数组中右面第一个比栈顶元素大的元素是i。

  即:如果求一个元素右边第一个更大元素,单调栈就是递增的,如果求一个元素右边第一个更小元素,单调栈就是递减的。

  文字描述理解起来有点费劲,接下来我画了一系列的图,来讲解单调栈的工作过程,大家再去思考,本题为什么是递增栈。

  使用单调栈主要有三个判断条件。

  • 当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况
  • 当前遍历的元素T[i]等于栈顶元素T[st.top()]的情况
  • 当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况

  以题目作为示例:

  739. 每日温度 - 力扣(Leetcode)

  给定一个整数数组 temperatures​ ,表示每天的温度,返回一个数组 answer​ ,其中 answer[i]​ 是指对于第 i​ 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0​ 来代替。

  示例 1:

输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
vector<int> result(temperatures.size(),0);
stack<int> st;
for(int i=0;i<temperatures.size();i++){
if(st.empty()) st.push(i);
else if(temperatures[i]<=temperatures[st.top()]) st.push(i);
else{
while(!st.empty()&&temperatures[i]>temperatures[st.top()]){ //!st.empty()要放在前面 先判断
result[st.top()]=i-st.top();
st.pop();
}
st.push(i);
}
}
return result;
}
};

  ‍

  ‍

  496. 下一个更大元素 I - 力扣(Leetcode)

  ​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 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
//返回的是元素 不是索引
//先找出下一个更大元素数组 然后再匹配num1
stack<int> st;
vector<int> res(nums2.size(),-1);
for(int i=0;i<nums2.size();i++){
if(st.empty()) st.push(i);
else if(nums2[i]<=nums2[st.top()]) st.push(i);
else{
while(!st.empty()&&nums2[st.top()]<nums2[i]){
res[st.top()]=nums2[i];
st.pop();
}
st.push(i);
}
}
vector<int>result(nums1.size(),-1);
for(int i=0;i<nums1.size();i++){
int index=0;
while(index<nums2.size()&&nums2[index]!=nums1[i]) index++;
result[i]=res[index];
}
return result;
}
};

  503. 下一个更大元素 II - 力扣(Leetcode)

  给定一个循环数组 nums​ ( nums[nums.length - 1]​ 的下一个元素是 nums[0]​ ),返回 nums​ 中每个元素的 ​下一个更大元素

  数字 x​ 的 下一个更大的元素 是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1​ 。

  示例 1:

输入: nums = [1,2,1]
输出: [2,-1,2]
解释: 第一个 1 的下一个更大的数是 2;
数字 2 找不到下一个更大的数; 
第二个 1 的下一个最大的数需要循环搜索,结果也是 2。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
vector<int> nextGreaterElements(vector<int>& nums) {
//循环用取余实现 总长度是2*nums.size()
stack<int> st;
vector<int> res(nums.size(),-1);
int nums_len=nums.size();
for(int i=0;i<nums.size()*2;i++){
if(st.empty()) st.push(i%nums_len);
else if(nums[i%nums_len]<=nums[st.top()%nums_len]) st.push(i%nums_len);
else{
while(!st.empty()&&nums[i%nums_len]>nums[st.top()%nums_len]){
res[st.top()%nums_len]=nums[i%nums_len];
st.pop();
}
st.push(i%nums_len);
}
}
return res;
}
};

  42. 接雨水 - 力扣(Leetcode)

  给定 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 个单位的雨水(蓝色部分表示雨水)。 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
public:
int trap(vector<int>& height) {
//单调栈 用三个元素来接雨水 先弹出一个mid 然后top mid i 计算h w
stack<int> st;
int sum=0;
for(int i=0;i<height.size();i++){
if(st.empty()) st.push(i);
else if(height[i]<=height[st.top()]) st.push(i);
else{
while(!st.empty()&&height[i]>height[st.top()]){
int mid=height[st.top()];
st.pop();
if(!st.empty()){
int w=i-st.top()-1;
int h=min(height[st.top()],height[i])-mid;
sum+=w*h;
}
}
st.push(i);
}
}
return sum;
}
};

  其他解法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
int trap(vector<int>& height) {
if(height.size()<=2) return 0;
vector<int> maxleft(height.size(),0);
vector<int> maxright(height.size(),0);
maxleft[0]=height[0];
maxright[height.size()-1]=height[height.size()-1];
for(int i=1;i<height.size();i++){
maxleft[i]=max(height[i],maxleft[i-1]);
}
for(int i=height.size()-2;i>=0;i--){
maxright[i]=max(height[i],maxright[i+1]);
}
int sum=0;
for(int i=0;i<height.size();i++){
int count=min(maxleft[i],maxright[i])-height[i];
if(count>0) sum+=count;

}
return sum;
}
};

  ‍

  84. 柱状图中最大的矩形 - 力扣(Leetcode)

  给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

  求在该柱状图中,能够勾勒出来的矩形的最大面积。

  示例 1:

  ​

输入:heights = [2,1,5,6,2,3]
输出:10
解释:最大的矩形为图中红色区域,面积为 10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
//从栈顶到栈底 从小到大 找左右两边比当前小的
stack<int> st;
heights.insert(heights.begin(),0);
heights.push_back(0);
int res=0;
for(int i=0;i<heights.size();i++){
if(st.empty()) st.push(i);
else{
while(heights[i]<heights[st.top()]){
int mid=st.top();
st.pop();
int w=i-st.top()-1;
int h=heights[mid];
res=max(res,w*h);
}
st.push(i);
}

}
return res;
}
};

  ‍


算法-单调栈
https://shanhainanhua.github.io/2023/08/25/算法-单调栈/
作者
wantong
发布于
2023年8月25日
许可协议