我们平时用的栈多,但是我们一般用的是什么呢?用来做有效的括号匹配?还是用来记录我们的二叉树的节点?
通过对栈的理解,我们学习一个新的概念–单调栈。所谓单调栈,就是单调递增或者单调递减的栈。
那么单调栈有什么作用呢?我们先看一个例题:
例题1
力扣:739. 每日温度
题目我们大致了解过了,我们开始做这个题,我们的思路如下:
- 题目的描述是,我们要找比第i个数大的下一个数,和如果没有比i大的下一个数。
- 如果找到了,我们要计算大多少,两个数的下标m,n相差多少。
- 相差的值就是结果数组中对应的值。
我们先写一个简单的步骤:
public int[] dailyTemperatures(int[] temperatures) {
int[] arr = new int[temperatures.length];
//....
return arr;
}
首先这肯定是我们要写的,不过中间该怎么写?这个问题怎么分析?当然我们可以两个for循环使劲暴力,这个方法往往是很有效的,但是这个题数组的长度是105,暴力的话时间复杂度为O(n2),跑一会应该也能过,但是面试的时候面试官就该让你回去等通知了。所以我们现在就介绍单调栈的写法,时间复杂度为O(n):
- 首先我们定一个栈,让在栈内数都是单调的,至于递增或者递减我们稍后再确定
- 我们根据题意,如果没有比第i天气温高的,那么arr[i]=0
- 如果有,我们就要记录相隔多久,那么就是两个天数的下标的差
对于这个题而言,单调栈递增或递减影响不大,我们实现的方式不同罢了,我们用一个单调递减的模拟: - 定义一个栈,让他记录下标
- 单调递减的时候,栈内的数据应该是这样:[75,71,69] (栈顶),那么当下一个比栈顶大的天气出现时,我们只需要减去两个下标的差,然后给栈顶元素下标赋值给arr数组对应的那天就行。
代码实现:
public int[] dailyTemperatures(int[] temperatures) {
int[] arr = new int[temperatures.length];
//定义单调栈,记录下标
Deque<Integer> stack = new ArrayDeque<>();
//将第一个下标放进,我们从第二天开始
stack.push(0);
for(int i = 1;i<arr.length;i++){
//如果栈不为空,并且当前天气比栈顶高,那么我们将栈顶出栈
while(!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]){
//栈顶气温的下标
int t = stack.pop();
//找到了比他大的,那么结果数组的第t天的值就是两个下标的差
arr[t] = i - t;
}
//将当前结果入栈
stack.push(i);
}
return arr;
}
对于例1:temperatures = [73,74,75,71,69,72,76,73]
,len = 8
我们用表格模拟一下每种情况:
i 的值 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
栈内数据: | [0] | [1] | [2] | [2,3] | [2,3,4] | [2,5] | [6] | [6,7] |
当i=2到i=5时,可以看到里面的变化,不断的将小的出栈,将大的进栈,不断的更新下标的差。
例题2
456. 132 模式
这个题比第一个难一些,但是理解之后也不是很难
题解:
public boolean find132pattern(int[] nums) {
//定义单调栈
Deque<Integer> stack = new ArrayDeque<>();
int k = Integer.MIN_VALUE;
for(int i = nums.length - 1;i>=0;i--){
if(nums[i] < k ) return true;
while(!stack.isEmpty() && stack.peek() < nums[i]){
k = stack.pop();
}
stack.push(nums[i]);
}
return false;
}
这个题目不在详细讲了,上面的单调栈理解之后这个题解也是可以看懂的。粘一个大佬的题解:
【宫水三叶の相信科学系列】详解为何使用「单调栈」来找最大的 K 是正确的