【LeetCode 热题 100】739. 每日温度——(解法一)单调栈+从右到左

Problem: 739. 每日温度

给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。

文章目录

整体思路

这段代码旨在高效地解决一个常见的算法问题:每日温度 (Daily Temperatures)。问题要求对于一个温度数组,计算并返回一个结果数组,其中每个元素代表需要等待多少天才能等到一个更暖和的天气。如果未来没有更暖和的天气,则等待天数为 0。

该算法巧妙地运用了 单调栈 (Monotonic Stack) 这一数据结构,并结合了 从右向左的逆向遍历,从而在线性时间内解决了问题。

  1. 核心思想:单调栈与逆向遍历

    • 逆向遍历 :算法从数组的末尾(最后一天)开始向前遍历。这样做的好处是,当我们处理第 i 天时,所有未来的日子(i+1, i+2, ...)及其相关信息(如它们的下一个更暖日的索引)都已经被处理过了。
    • 单调栈 :算法维护一个栈,这个栈里存储的是日期的索引 。最关键的是,这个栈始终保持一个单调递减的性质(从栈底到栈顶,索引对应的温度值是递减的)。
  2. 算法执行步骤

    • 初始化一个结果数组 ans(默认值为0)和一个空栈 st
    • i = n-10 遍历 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 左侧)的日期提供了一个潜在的"下一个更暖日"的候选。

通过这种方式,每个元素的索引最多入栈一次、出栈一次,确保了算法的线性时间效率。

完整代码

java 复制代码
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)

  1. 外层循环for (int i = n - 1; i >= 0; i--) 遍历整个 temperatures 数组一次,执行 N 次。
  2. 内层循环while 循环中的 pop 操作是关键。虽然它在 for 循环内部,但需要分析其总的执行次数。
    • 每个数组元素的索引 最多只会被 push 进栈一次。
    • 每个索引也最多只会被 pop 出栈一次。
    • 因此,在整个算法的生命周期中,pushpop 的总操作次数都是 O(N) 级别。
  3. 综合分析
    • for 循环本身是 O(N)。
    • 所有 while 循环中的 pop 操作加起来也是 O(N)。
    • while 循环的成本均摊for 循环的每次迭代中,均摊时间复杂度为 O(1)。
    • 因此,总的时间复杂度是 N * O(1) = O(N)

空间复杂度:O(N)

  1. 主要存储开销 :算法使用了一个双端队列 st 作为栈。
  2. 空间大小 :在最坏的情况下,栈需要存储所有元素的索引。这种情况发生在一个严格递减的温度数组中,例如 [80, 70, 60, 50, 40]。在这种情况下,没有元素会被 while 循环弹出,所有 N 个索引都会被依次压入栈中。
  3. 结果数组ans 数组是输出结果,其空间为 O(N)。在复杂度分析中,通常不计入输出结果所占用的空间,只分析额外辅助空间。

综合分析

算法所需的额外辅助空间主要由单调栈 st 决定。在最坏情况下,其大小与输入规模 N 成线性关系。因此,空间复杂度为 O(N)

参考灵神

相关推荐
2202_756749692 分钟前
02 基于sklearn的机械学习-KNN算法、模型选择与调优(交叉验证、朴素贝叶斯算法、拉普拉斯平滑)、决策树(信息增益、基尼指数)、随机森林
python·算法·决策树·随机森林·机器学习·sklearn
R cddddd3 分钟前
Spring Boot Admin 监控模块笔记-实现全链路追踪
java·笔记·spring cloud
ATaylorSu5 分钟前
经典算法之美:冒泡排序的优雅实现
开发语言·笔记·学习·算法
菜鸡nan1 小时前
23th Day| 39.组合总和,40.组合总和II,131.分割回文串
算法·leetcode·职场和发展
冷月葬花~1 小时前
day37 卡码网52. 携带研究材料 力扣518.零钱兑换II 力扣377. 组合总和 Ⅳ 卡码网57. 爬楼梯
算法
qq_513970441 小时前
力扣 hot100 Day63
数据结构·算法·leetcode
唐叔在学习1 小时前
Maven - 并行安全无重复打包构建原理揭秘
java·maven·maven并行构建·maven反应堆原理
山间小僧1 小时前
「查漏补缺」ZGC相关内容整理
java·jvm·后端
bluebonnet271 小时前
【python】转移本地安装的python包
java·python·eureka
lifallen1 小时前
AbstractExecutorService:Java并发核心模板解析
java·开发语言·数据结构·算法