LeetCode 739:每日温度——为什么单调栈要持续弹出

题目描述

给定一个整数数组 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] = 1answer[3] = 2
6 76 76 > 72,弹出 5;76 > 75,弹出 2;6 入栈 [6] answer[5] = 1answer[2] = 4
7 73 73 < 76,7 入栈 [6,7] -

最后栈里剩下的下标 67,说明它们后面没有更高温度。因为 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 可以同时解决 7169。所以这里应该用 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 + 当前下标入栈 的结构。

相关推荐
2601_955759881 小时前
如何降低 Claude API 批量生产返工率
大数据·人工智能·算法
無限進步D1 小时前
Java 函数式编程(Lambda)
java·开发语言
rannn_1111 小时前
【力扣hot100】链表专题下|138、148、23、146
java·算法·leetcode·链表·开发
ly76891 小时前
分布式一致性算法详解:从 2PC、3PC 到 Paxos、Raft、ZAB
分布式·算法
xzlAwin1 小时前
Win10安装Go语言多版本管理器g工具
开发语言·golang
for_ever_love__1 小时前
python基础语法学习: 文件操作
python·学习
(initial)1 小时前
C-05. Kernel Fusion 代价边界:少写回 vs 寄存器压力与 occupancy
c语言·开发语言·cuda
阿维的博客日记2 小时前
保姆级教程-BBPE分词算法
算法·bbpe
青春不败 177-3266-05202 小时前
基于R、Python的Copula变量相关性分析及AI大模型应用
人工智能·python·r语言·贝叶斯·统计学·copula