【LeetCode Hot100 刷题日记 (72/100)】739. 每日温度 —— 单调栈🧠

3 阅读6分钟

📌 题目链接:739. 每日温度 - 力扣(LeetCode)

🔍 难度:中等 | 🏷️ 标签:数组、单调栈

⏱️ 目标时间复杂度:O(n)

💾 空间复杂度:O(n) (单调栈)或 O(1) (暴力优化版,但本题更推荐单调栈)


🎯 一句话题意:给定每日气温数组,对每一天,求下一次气温更高是在几天后。若没有,则填 0。

这道题是单调栈的经典入门题,也是面试高频考点!它完美展示了“找右边第一个更大元素”这类问题的标准解法。掌握它,等于掌握了单调栈的“灵魂”。


🔍 题目分析

输入是一个长度为 n 的整数数组 temperatures,每个元素代表某天的温度(30 ≤ T ≤ 100)。
要求输出一个同样长度的数组 answer,其中:

  • answer[i] = j - i,其中 j 是满足 j > itemperatures[j] > temperatures[i]最小下标
  • 若不存在这样的 j,则 answer[i] = 0

✅ 示例回顾

输入: [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]
  • 第 0 天(73°)→ 第 1 天(74°)更高 → 等 1 天;
  • 第 2 天(75°)→ 要等到第 6 天(76°)才更高 → 等 4 天;
  • 第 6、7 天之后无更高温度 → 填 0。

🧱 核心算法及代码讲解:单调栈(Monotonic Stack)

💡 核心思想用栈维护一个“递减”的下标序列,当遇到一个更大的温度时,就“清算”所有比它小的元素。

📌 为什么用单调栈?

这类“找右边第一个更大/更小元素”的问题,天然适合单调栈

  • 暴力法:对每个 i,向右遍历找第一个更大的 j → O(n²),超时(n=1e5)。
  • 单调栈:每个元素最多入栈一次、出栈一次O(n) 时间!

📐 单调栈的性质(本题)

  • 栈中存的是下标(不是温度值!),便于计算天数差。
  • 从栈底到栈顶,温度严格递减(非严格也可,但本题用严格更清晰)。
  • 当新元素 temperatures[i] 比栈顶对应的温度大时,说明找到了“下一个更高温度”!

🧾 算法步骤(正向遍历)

  1. 初始化空栈 s 和答案数组 ans(全 0)。

  2. 从左到右遍历每一天 i

    • 循环检查:只要栈非空 当前温度 > 栈顶下标对应温度

      • 弹出栈顶 prevIndex
      • 设置 ans[prevIndex] = i - prevIndex(这就是等待天数!);
    • 将当前下标 i 入栈。

  3. 遍历结束,栈中剩余元素对应的 ans 保持为 0(符合题意)。

🧪 为什么正确?

  • 关键洞察:当 temperatures[i] 大于 temperatures[prevIndex] 时,i 就是 prevIndex 右边第一个更大的温度!

    • 因为如果中间存在更近的 j < i 使得 T[j] > T[prevIndex],那么 prevIndex 早就被 j 弹出了。
  • 单调性保证:栈中元素对应的温度递减,所以一旦当前温度能“打败”栈顶,就能连续打败多个栈顶元素(如示例中 i=6 打败 5 和 2)。

💻 C++ 核心代码(带详细行注释)

vector<int> dailyTemperatures(vector<int>& temperatures) {
    int n = temperatures.size();
    vector<int> ans(n);           // 初始化答案数组,全0
    stack<int> s;                 // 单调栈:存储下标

    for (int i = 0; i < n; ++i) {
        // 当前温度比栈顶对应温度高 → 找到“下一个更高温度”
        while (!s.empty() && temperatures[i] > temperatures[s.top()]) {
            int prevIndex = s.top();  // 待更新的下标
            ans[prevIndex] = i - prevIndex; // 等待天数 = 当前下标 - 之前下标
            s.pop();                  // 弹出已处理的下标
        }
        s.push(i); // 当前下标入栈(无论是否弹出过别人)
    }
    return ans;
}

注意:栈中始终维护的是“尚未找到更高温度”的那些天的下标。


🧩 解题思路(分步拆解)

步骤操作说明
1️⃣初始化创建答案数组 ans(默认全 0),创建空栈 s
2️⃣正向遍历i = 0n-1
3️⃣比较 & 弹栈T[i] > T[s.top()],则不断弹栈并更新 ans[prevIndex] = i - prevIndex
4️⃣入栈将当前 i 压入栈(代表它还没找到更高的温度)
5️⃣返回结果遍历结束,ans 即为所求

🔄 类比理解:想象你在排队,每个人举着自己的温度牌。你(当前 i)往前看,如果前面有人温度比你低,你就告诉他:“你的等待结束了,等了 i - 他的位置 天!” 然后他离开队伍。你继续往前看,直到遇到温度 ≥ 你的人,然后你站到他后面排队。


📊 算法分析

维度分析
时间复杂度O(n) 每个下标最多入栈 1 次、出栈 1 次,总操作 ≤ 2n
空间复杂度O(n) 最坏情况(温度递减),栈存所有下标
适用场景所有“找右侧第一个更大/更小元素”问题 如:接雨水、柱状图最大矩形、股票跨度等
面试价值⭐⭐⭐⭐⭐ 必考!需能手写 + 讲清单调性 + 举例说明

💡 延伸思考

  • 如果题目改成“上一次更高温度”,怎么办?→ 反向遍历 + 单调栈
  • 如果温度范围很大(如 1~1e9),还能用方法一(next 数组)吗?→ 不能!必须用单调栈

💻 完整代码

C++

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& temperatures) {
        int n = temperatures.size();
        vector<int> ans(n);
        stack<int> s;
        for (int i = 0; i < n; ++i) {
            while (!s.empty() && temperatures[i] > temperatures[s.top()]) {
                int previousIndex = s.top();
                ans[previousIndex] = i - previousIndex;
                s.pop();
            }
            s.push(i);
        }
        return ans;
    }
};

// 测试
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    Solution sol;
    vector<int> t1 = {73,74,75,71,69,72,76,73};
    auto res1 = sol.dailyTemperatures(t1);
    for (int x : res1) cout << x << " "; // 输出: 1 1 4 2 1 1 0 0\n
    cout << "\n";

    vector<int> t2 = {30,40,50,60};
    auto res2 = sol.dailyTemperatures(t2);
    for (int x : res2) cout << x << " "; // 输出: 1 1 1 0\n
    cout << "\n";

    vector<int> t3 = {30,60,90};
    auto res3 = sol.dailyTemperatures(t3);
    for (int x : res3) cout << x << " "; // 输出: 1 1 0\n
    cout << "\n";

    return 0;
}

JavaScript

/**
 * @param {number[]} temperatures
 * @return {number[]}
 */
var dailyTemperatures = function(temperatures) {
    const n = temperatures.length;
    const ans = new Array(n).fill(0);
    const stack = []; // 存储下标

    for (let i = 0; i < n; i++) {
        // 当前温度更高,清算栈顶
        while (stack.length > 0 && temperatures[i] > temperatures[stack[stack.length - 1]]) {
            const prevIndex = stack.pop();
            ans[prevIndex] = i - prevIndex;
        }
        stack.push(i);
    }
    return ans;
};

// 测试
console.log(dailyTemperatures([73,74,75,71,69,72,76,73])); // [1,1,4,2,1,1,0,0]
console.log(dailyTemperatures([30,40,50,60]));             // [1,1,1,0]
console.log(dailyTemperatures([30,60,90]));                // [1,1,0]

🌟 本期完结,下期见!🔥

👉 点赞收藏加关注,新文更新不迷路。关注专栏【算法】LeetCode Hot100刷题日记,持续为你拆解每一道热题的底层逻辑与面试技巧!

💬 欢迎留言交流你的解法或疑问!一起进步,冲向 Offer!💪

📌 记住:当你在刷题时,不要只看答案,要像写这篇文章一样,深入思考每一步背后的原理、优化空间和面试价值。这才是真正提升算法能力的方式!