本次主要总结利用单调栈解决leetcode中的第739题每日温度、第496题下一个更大的元素I、第503题下一个更大的元素II。
单调栈主要解决的几种问题:
- 比当前元素更大的下一个元素
- 比当前元素更大的前一个元素
- 比当前元素更小的下一个元素
- 比当前元素更小的前一个元素
需要思考的几个问题:
- 如何操作单调栈
- 严格单调栈还是非严格单调栈
- 单调递增栈还是单调递减栈
先以739题为例看看如何操作单调栈,题目地址:739每日温度
如何操作单调栈
第739题的题意简单来说就是,给定一个数组nums
,找到每个元素nums[i]
的下一个比nums[i]
大的元素对应的索引的差值,比如[73,74,75,71,69,72,76,73]
,输出[1,1,0,2,1,1,0,0]
结题思路:
最容易想到的结题思路是从前向后遍历,每次遍历一个元素,找到后面比它大的元素,但是很明显,有些元素我们需要重复遍历,增加了时间复杂度。比如找比75大的元素,我们需要遍历[71,69,72,76]
,找比71大的元素,我们需要遍历69,72
,显然有重复遍历的区域。如果可以减少这部分的遍历次数,就可以提高整体的运行效率。
首先考虑初始状态,最后一个元素的输出肯定是0,因为最后一个元素找不到后面比它大的元素(ps.这个要看题意,比如503题重新定义了数组),我们考虑从后向前遍历,我们想象这样的场景,把数组想象成站成一队的人,这些人面对你站成一列,把数组中的元素想象成人的身高,如何求第2个人的下一个比他高的人呢?因为比第2个人矮的人我们都看不到,第一个露出来的就是答案
所以单调栈中存储一个从上向下递增的索引,每次遍历一个元素nums[i]
的时候,
- 判断栈中是否有比
nums[i]
小的元素,如果有就出栈(因为本次操作之后,nums[i]
会进栈,再向前遍历的时候,没有必要比较后面比nums[i]
的元素了) - 直到找到比
nums[i]
大的第一个元素,计算索引的差值 - 将
nums[i]
入栈,向前遍历,执行步骤1。
对于上面的例子,请看下图进行遍历的过程
简要介绍:第一行是正在遍历的数组,第二行是最后输出结果的数组,蓝色填充为遍历后确定的结果,绿色填充为栈中的元素(存储的原数组索引),粉色填充表示第一个比当前元素大的索引,紫色填充表示出栈的元素
从图中出栈的元素可以看出,对于后面遍历的元素,减少了这些出栈元素的匹配过程,降低了时间复杂度。
Java语言的代码
public static int[] dailyTemperatures2(int[] T) {
Stack<Integer> s = new Stack<>();//栈中存储索引值,对应的栈顶的元素最小
int[] ans = new int[T.length];//结果输出的数组,初始化都是0
for(int i = T.length - 1;i >= 0;--i){//从后向前遍历数组
while (!s.empty() && T[i] >= T[s.peek()])s.pop();//栈不空,栈顶索引对应的元素小于等于当前元素,出栈,避免之后的元素重复计算
ans[i] = s.empty()? 0 : s.peek() - i;//栈中元素是否为空,不空就是栈顶元素减去当前索引
s.push(i);//当前元素入栈
}
return ans;
}
Python3代码:
def dailyTemperatures(self, T: List[int]) -> List[int]:
ans = [0] * len(T) # 初始化结果数组
stack = [] # 存储索引的单调栈
for i in range(len(T) - 1, -1, -1): # 从后向前遍历,步长-1,
while stack and T[i] >= T[stack[-1]]:
stack.pop()
if stack:
ans[i] = stack[-1] - i
stack.append(i)
return ans
c++代码:
vector<int> dailyTemperatures(vector<int>& T) {
vector<int> ans(T.size(),0);//初始化结果栈
stack<int> s;
for(int i = T.size() - 1;i >= 0;i--){
while(!s.empty() && T[s.top()] <= T[i])s.pop();
ans[i] = s.empty() ? 0 : s.top() - i;
s.push(i);
}
return ans;
}
虽然方法是一样的,但是python3和c++的代码用时明显大于java的,不是很懂,欢迎大佬补充
归纳单调栈解决几种问题
通过该题的步骤我们大致可以归纳一下单调栈解决的几类问题
- 如果是比该元素更大的元素,使用单调递增栈,否则,使用单调递减栈;
- 对于严格单调还是非严格单调,具体看题目要求,对于相等的元素应该如何处理
其他应用单调栈的题目:
496.下一个更大的元素I
503.下一个更大的元素II
42.接雨水
901.股票价格跨度
239.滑动窗口最大值
84.柱状图中最大的矩形