programmercarl.com/algo/monoto...
给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。
示例 1:
ini
输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]
示例 2:
ini
输入: temperatures = [30,40,50,60]
输出: [1,1,1,0]
示例 3:
ini
输入: temperatures = [30,60,90]
输出: [1,1,0]
提示:
1 <= temperatures.length <= 10530 <= temperatures[i] <= 100
思路
首先想到的当然是暴力解法,两层for循环,把至少需要等待的天数就搜出来了。时间复杂度是O(n^2)
那么接下来在来看看使用单调栈的解法。
那有同学就问了,我怎么能想到用单调栈呢? 什么时候用单调栈呢?
通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了。时间复杂度为O(n)。
例如本题其实就是找找到一个元素右边第一个比自己大的元素,此时就应该想到用单调栈了。
那么单调栈的原理是什么呢?为什么时间复杂度是O(n)就可以找到每一个元素的右边第一个比它大的元素位置呢?
单调栈的本质是空间换时间,因为在遍历的过程中需要用一个栈来记录右边第一个比当前元素高的元素,优点是整个数组只需要遍历一次。
更直白来说,就是用一个栈来记录我们遍历过的元素,因为我们遍历数组的时候,我们不知道之前都遍历了哪些元素,以至于遍历一个元素找不到是不是之前遍历过一个更小的,所以我们需要用一个容器(这里用单调栈)来记录我们遍历过的元素。
在使用单调栈的时候首先要明确如下几点:
- 单调栈里存放的元素是什么?
单调栈里只需要存放元素的下标i就可以了,如果需要使用对应的元素,直接Ti就可以获取。
- 单调栈里元素是递增呢? 还是递减呢?
注意以下讲解中,顺序的描述为 从栈头到栈底的顺序,因为单纯的说从左到右或者从前到后,不说栈头朝哪个方向的话,大家一定比较懵。
这里我们要使用递增循序(再强调一下是指从栈头到栈底的顺序),因为只有递增的时候,栈里要加入一个元素i的时候,才知道栈顶元素在数组中右面第一个比栈顶元素大的元素是i。
即:如果求一个元素右边第一个更大元素,单调栈就是递增的,如果求一个元素右边第一个更小元素,单调栈就是递减的。
文字描述理解起来有点费劲,接下来我画了一系列的图,来讲解单调栈的工作过程,大家再去思考,本题为什么是递增栈。
使用单调栈主要有三个判断条件。
- 当前遍历的元素Ti小于栈顶元素Tst.top()的情况
- 当前遍历的元素Ti等于栈顶元素Tst.top()的情况
- 当前遍历的元素Ti大于栈顶元素Tst.top()的情况
把这三种情况分析清楚了,也就理解透彻了。
接下来我们用temperatures = 73, 74, 75, 71, 71, 72, 76, 73为例来逐步分析,输出应该是 1, 1, 4, 2, 1, 1, 0, 0。
首先先将第一个遍历元素加入单调栈

加入T1 = 74,因为T1 > T0(当前遍历的元素Ti大于栈顶元素Tst.top()的情况)。
我们要保持一个递增单调栈(从栈头到栈底),所以将T0弹出,T1加入,此时result数组可以记录了,result0 = 1,即T0右面第一个比T0大的元素是T1。

加入T2,同理,T1弹出

加入T3,T3 < T2 (当前遍历的元素Ti小于栈顶元素Tst.top()的情况),加T3加入单调栈。

加入T4,T4 == T3 (当前遍历的元素Ti等于栈顶元素Tst.top()的情况),此时依然要加入栈,不用计算距离,因为我们要求的是右面第一个大于本元素的位置,而不是大于等于!

加入T5,T5 > T4 (当前遍历的元素Ti大于栈顶元素Tst.top()的情况),将T4弹出,同时计算距离,更新result 
T4弹出之后, T5 > T3 (当前遍历的元素Ti大于栈顶元素Tst.top()的情况),将T3继续弹出,同时计算距离,更新result 
直到发现T5小于Tst.top(),终止弹出,将T5加入单调栈

加入T6,同理,需要将栈里的T5,T2弹出

同理,继续弹出

此时栈里只剩下了T6

加入T7, T7 < T6 直接入栈,这就是最后的情况,result数组也更新完了。

此时有同学可能就疑惑了,那result6 , result7怎么没更新啊,元素也一直在栈里。
其实定义result数组的时候,就应该直接初始化为0,如果result没有更新,说明这个元素右面没有更大的了,也就是为0。
以上在图解的时候,已经把,这三种情况都做了详细的分析。
- 情况一:当前遍历的元素Ti小于栈顶元素Tst.top()的情况
- 情况二:当前遍历的元素Ti等于栈顶元素Tst.top()的情况
- 情况三:当前遍历的元素Ti大于栈顶元素Tst.top()的情况
通过以上过程,大家可以自己再模拟一遍,就会发现:只有单调栈递增(从栈口到栈底顺序),就是求右边第一个比自己大的,单调栈递减的话,就是求右边第一个比自己小的。
kotlin
class Solution {
fun dailyTemperatures(temperatures: IntArray): IntArray {
val stack = Stack<Int>()
val resultArray = IntArray(size = temperatures.size, init = { 0 })
/**
如果当前遍历的元素 大于栈顶元素,表示 栈顶元素的 右边的最大的元素就是 当前遍历的元素,
所以弹出 栈顶元素,并记录
如果栈不空的话,还要考虑新的栈顶与当前元素的大小关系
否则的话,可以直接入栈。
注意,单调栈里 加入的元素是 下标。
*/
for (i in temperatures.indices) {
if (stack.isEmpty()) {
stack.push(i)
} else {
while (stack.isNotEmpty()) {
val curTemp = temperatures[i]
val topTemp = temperatures[stack.peek()]
if (curTemp <= topTemp) {
break
} else {
resultArray[stack.peek()] = i - stack.peek()
stack.pop()
}
}
stack.push(i)
}
}
return resultArray
}
}
- 时间复杂度:O(n)
- 空间复杂度:O(n)