题目描述
给定一个整数数组 temperatures,表示每天的温度,返回一个数组 answer。
其中 answer[i] 表示:对于第 i 天,下一个更高温度会出现在几天后。如果后面没有更高温度,就在该位置填 0。
例如:
temperatures = [73,74,75,71,69,72,76,73]
answer = [1,1,4,2,1,1,0,0]
最初的思路:用栈保存温度和下标
最开始想到的是自定义一个 Node,同时保存温度值和下标:
class Node {
int val;
int idx;
}
遍历数组时,把还没有确定答案的温度放进栈里。当遇到一个更高的温度时,就弹出栈顶,并计算它距离当前下标的天数。
这个方向本身是对的:这道题确实适合用栈来维护"还没找到更高温度的那些天"。
但真正容易出问题的地方在于:当前温度可能不只解决栈顶一个元素。
问题出在哪里
看这个例子:
temperatures = [73,74,75,71,69,72,76,73]
当遍历到温度 72 时,也就是下标 5:
下标 3 -> 温度 71
下标 4 -> 温度 69
下标 5 -> 温度 72
72 不只是比 69 高,它也比 71 高。
所以它应该同时解决下标 4 和下标 3:
answer[4] = 5 - 4 = 1
answer[3] = 5 - 3 = 2
如果这里只用一次 if:
if (temperatures[i] > stack.peek().val) {
Node node = stack.pop();
ans[node.idx] = i - node.idx;
}
就只能弹出一个栈顶元素,可能会漏掉更早也能被当前温度解决的下标。
因此,这里不能只"判断一次",而应该"只要当前温度还能解决栈顶,就持续弹出"。
正确思路:维护单调递减栈
栈中保存的是还没有找到更高温度的下标。
为了让当前温度能够判断自己是否能解决前面的某一天,栈中对应的温度应该保持单调递减。
遍历到第 i 天时:
- 如果当前温度大于栈顶下标对应的温度,说明当前这一天就是栈顶那一天等到的更高温度。
- 弹出栈顶下标
idx,并填写answer[idx] = i - idx。 - 继续比较新的栈顶,因为当前温度可能还能解决更多天。
- 当前温度处理完后,把当前下标
i入栈,等待未来更高温度。
这个过程对应的核心逻辑是:
while 栈不为空,并且当前温度 > 栈顶下标对应的温度:
弹出栈顶下标
填写这个旧下标的答案
当前下标入栈
注意答案要填到"被弹出的旧下标",而不是当前下标附近的位置。
手推过程
仍然用这个例子:
[73,74,75,71,69,72,76,73]
栈中保存下标,右侧为栈顶。
| 当前下标 | 当前温度 | 操作 | 栈 | answer 变化 |
|---|---|---|---|---|
| 0 | 73 | 入栈 | [0] |
- |
| 1 | 74 | 74 > 73,弹出 0;1 入栈 | [1] |
answer[0] = 1 |
| 2 | 75 | 75 > 74,弹出 1;2 入栈 | [2] |
answer[1] = 1 |
| 3 | 71 | 71 < 75,3 入栈 | [2,3] |
- |
| 4 | 69 | 69 < 71,4 入栈 | [2,3,4] |
- |
| 5 | 72 | 72 > 69,弹出 4;72 > 71,弹出 3;5 入栈 | [2,5] |
answer[4] = 1,answer[3] = 2 |
| 6 | 76 | 76 > 72,弹出 5;76 > 75,弹出 2;6 入栈 | [6] |
answer[5] = 1,answer[2] = 4 |
| 7 | 73 | 73 < 76,7 入栈 | [6,7] |
- |
最后栈里剩下的下标 6 和 7,说明它们后面没有更高温度。因为 int[] answer 默认值就是 0,所以不需要额外处理。
最终结果:
[1,1,4,2,1,1,0,0]
Java 代码
栈里只需要保存下标,不一定要额外定义 Node。因为温度值可以通过 temperatures[idx] 取到。
import java.util.ArrayDeque;
import java.util.Deque;
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int n = temperatures.length;
int[] answer = new int[n];
Deque<Integer> stack = new ArrayDeque<>();
for (int i = 0; i < n; i++) {
while (!stack.isEmpty()
&& temperatures[i] > temperatures[stack.peek()]) {
int idx = stack.pop();
answer[idx] = i - idx;
}
stack.push(i);
}
return answer;
}
}
如果保留 Node 写法也可以,只是下标本身已经足够表达栈里的信息。
我的原始错因
这题的坑主要有三个。
第一,遇到更高温度时不能只弹一次。
当前温度可能同时解决多个之前的下标,例如 72 可以同时解决 71 和 69。所以这里应该用 while,而不是 if。
第二,答案应该填到被弹出的下标。
弹出的 idx 表示"第 idx 天终于等到了更高温度",所以应该写:
answer[idx] = i - idx;
而不是写到 answer[i - 1]。
第三,当前下标每一轮都要入栈。
即使当前温度没有解决任何旧下标,它自己也可能被未来某一天解决,所以当前下标应该在本轮处理结束后入栈。
建议测试用例
[73,74,75,71,69,72,76,73] -> [1,1,4,2,1,1,0,0]
这个用例检查当前温度能否连续弹出多个栈顶元素。
[30,40,50,60] -> [1,1,1,0]
这个用例检查严格递增时是否能及时填答案。
[30,60,90] -> [1,1,0]
这个用例检查短数组和连续升温。
[90,80,70] -> [0,0,0]
这个用例检查一直没有更高温度时,默认值 0 是否保留。
[70,70,71] -> [2,1,0]
这个用例检查相等温度不能算作"更高温度",只有严格大于才可以弹栈。
复杂度分析
- 时间复杂度:
O(n)。每个下标最多入栈一次、出栈一次。 - 空间复杂度:
O(n)。最坏情况下温度一直递减,所有下标都会留在栈里。
总结
这道题的关键是:栈里保存还没找到更高温度的下标,并让这些下标对应的温度保持单调递减。
遍历到当前温度时,只要它比栈顶温度高,就说明栈顶那一天的答案已经找到了;如果它还能继续解决新的栈顶,就继续弹出。
写单调栈时可以重点自检三点:当前元素是否可能一次解决多个栈顶元素,答案应该填到哪个下标,当前元素处理完后是否也需要入栈。把这三点理顺,代码就会自然变成 while + pop + 当前下标入栈 的结构。