【LeetCode 热题 100】739. 每日温度——(解法一)单调栈+从右到左
Problem: 739. 每日温度
给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。
文章目录
- 整体思路
- 完整代码
- 时空复杂度
- 时间复杂度:O(N)
- 空间复杂度:O(N)
整体思路
这段代码旨在高效地解决一个常见的算法问题:每日温度 (Daily Temperatures)。问题要求对于一个温度数组,计算并返回一个结果数组,其中每个元素代表需要等待多少天才能等到一个更暖和的天气。如果未来没有更暖和的天气,则等待天数为 0。
该算法巧妙地运用了 单调栈 (Monotonic Stack) 这一数据结构,并结合了 从右向左的逆向遍历,从而在线性时间内解决了问题。
-
核心思想:单调栈与逆向遍历
- 逆向遍历:算法从数组的末尾(最后一天)开始向前遍历。这样做的好处是,当我们处理第
i
天时,所有未来的日子(i+1
,i+2
, …)及其相关信息(如它们的下一个更暖日的索引)都已经被处理过了。 - 单调栈:算法维护一个栈,这个栈里存储的是日期的索引。最关键的是,这个栈始终保持一个单调递减的性质(从栈底到栈顶,索引对应的温度值是递减的)。
- 逆向遍历:算法从数组的末尾(最后一天)开始向前遍历。这样做的好处是,当我们处理第
-
算法执行步骤:
- 初始化一个结果数组
ans
(默认值为0)和一个空栈st
。 - 从
i = n-1
到0
遍历temperatures
数组:
a. 维护单调性:对于当前温度t = temperatures[i]
,查看栈顶的索引st.peek()
。只要栈不为空,并且当前温度t
大于或等于栈顶索引所对应的温度,就不断地将栈顶元素弹出。- 为什么要这样做? 因为如果栈顶的某天
j
的温度不比第i
天高,那么对于任何在i
之前的日子(例如i-1
),如果它在寻找一个更暖和的日子,第i
天显然是比第j
天更近、更好的选择。因此,第j
天对于i
之前的日子来说,已经没有作为“下一个更暖日”的价值了,可以被安全地从栈中移除。
b. 计算结果:在执行完上一步的while
循环后,栈的状态有两种可能: - 栈不为空:此时栈顶的索引
st.peek()
就是从第i
天向右看,遇到的第一个比temperatures[i]
更暖的日子的索引。因此,等待的天数就是st.peek() - i
。 - 栈为空:这说明在
i
之后的所有日子里,都找不到一个更暖和的天气。结果ans[i]
保持默认值0
即可。
c. 当前索引入栈:将当前日期的索引i
压入栈中。这为后续(即i
左侧)的日期提供了一个潜在的“下一个更暖日”的候选。
- 为什么要这样做? 因为如果栈顶的某天
- 初始化一个结果数组
通过这种方式,每个元素的索引最多入栈一次、出栈一次,确保了算法的线性时间效率。
完整代码
class Solution {/*** 给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,* 其中 answer[i] 是指在第 i 天之后,才会有更高的温度。如果气温在这之后都不会升高,请在该位置用 0 来代替。* @param temperatures 每日温度数组* @return 每日等待天数数组*/public int[] dailyTemperatures(int[] temperatures) {int n = temperatures.length;// ans 数组用于存储结果,Java 中 int 数组默认值为 0,正好满足题目要求。int[] ans = new int[n];// st 是一个单调栈,存储的是日期的索引。// 从栈底到栈顶,索引对应的温度是单调递减的。Deque<Integer> st = new ArrayDeque<>();// 核心步骤:从右向左遍历温度数组for (int i = n - 1; i >= 0; i--) {int t = temperatures[i];// 步骤 1: 维护栈的单调性// 当栈不为空,且当前温度 t 大于或等于栈顶索引对应的温度时,// 将栈顶元素弹出。因为这个栈顶元素对于 i 左边的任何一天来说,// 都不再是“下一个更暖日”的候选者了。while (!st.isEmpty() && t >= temperatures[st.peek()]) {st.pop();}// 步骤 2: 计算结果// 经过上一步的清理,如果栈不为空,则栈顶元素就是 i 右侧第一个更暖日的索引if (!st.isEmpty()) {ans[i] = st.peek() - i;}// 步骤 3: 当前索引入栈// 将当前天的索引压入栈中,作为其左边日子的一个潜在答案st.push(i);}return ans;}
}
时空复杂度
时间复杂度:O(N)
- 外层循环:
for (int i = n - 1; i >= 0; i--)
遍历整个temperatures
数组一次,执行N
次。 - 内层循环:
while
循环中的pop
操作是关键。虽然它在for
循环内部,但需要分析其总的执行次数。- 每个数组元素的索引最多只会被
push
进栈一次。 - 每个索引也最多只会被
pop
出栈一次。 - 因此,在整个算法的生命周期中,
push
和pop
的总操作次数都是 O(N) 级别。
- 每个数组元素的索引最多只会被
- 综合分析:
for
循环本身是 O(N)。- 所有
while
循环中的pop
操作加起来也是 O(N)。 - 将
while
循环的成本均摊到for
循环的每次迭代中,均摊时间复杂度为 O(1)。 - 因此,总的时间复杂度是
N * O(1)
= O(N)。
空间复杂度:O(N)
- 主要存储开销:算法使用了一个双端队列
st
作为栈。 - 空间大小:在最坏的情况下,栈需要存储所有元素的索引。这种情况发生在一个严格递减的温度数组中,例如
[80, 70, 60, 50, 40]
。在这种情况下,没有元素会被while
循环弹出,所有N
个索引都会被依次压入栈中。 - 结果数组:
ans
数组是输出结果,其空间为 O(N)。在复杂度分析中,通常不计入输出结果所占用的空间,只分析额外辅助空间。
综合分析:
算法所需的额外辅助空间主要由单调栈 st
决定。在最坏情况下,其大小与输入规模 N
成线性关系。因此,空间复杂度为 O(N)。
参考灵神