Leetcode 41

1 题目

739. 每日温度

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

示例 1:

复制代码
输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]

示例 2:

复制代码
输入: temperatures = [30,40,50,60]
输出: [1,1,1,0]

示例 3:

复制代码
输入: temperatures = [30,60,90]
输出: [1,1,0]

提示:

  • 1 <= temperatures.length <= 105
  • 30 <= temperatures[i] <= 100

2 代码实现

cpp 复制代码
/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
int* dailyTemperatures(int* temperatures, int temperaturesSize, int* returnSize) {
    // 设置返回数组大小
    *returnSize = temperaturesSize;
    // 初始化结果数组,默认值为0
    int* answer = (int*)malloc(sizeof(int) * temperaturesSize);
    for (int i = 0; i < temperaturesSize; i++) {
        answer[i] = 0;
    }
    
    // 单调栈:存储温度数组的索引,维持栈内温度单调递减
    int* stack = (int*)malloc(sizeof(int) * temperaturesSize);
    int top = -1;  // 栈顶指针,-1表示栈为空
    
    for (int i = 0; i < temperaturesSize; i++) {
        // 当栈非空且当前温度高于栈顶索引对应的温度时,弹出栈顶并计算天数
        while (top != -1 && temperatures[i] > temperatures[stack[top]]) {
            int prev_idx = stack[top--];  // 弹出栈顶索引
            answer[prev_idx] = i - prev_idx;  // 计算间隔天数
        }
        // 当前索引入栈
        stack[++top] = i;
    }
    
    free(stack);  // 释放栈内存
    return answer;
}
cpp 复制代码
/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
int* dailyTemperatures(int* temperatures, int temperaturesSize, int* returnSize) {
    // 1. 初始化返回结果相关
    *returnSize = temperaturesSize;  // 设置返回数组的大小
    int* answer = (int*)malloc(sizeof(int) * temperaturesSize);
    for (int i = 0; i < temperaturesSize; i++) {
        answer[i] = 0;  // 默认值为0(没有更高温度)
    }
    
    // 2. 初始化单调栈(存储温度的索引)
    int* stack = (int*)malloc(sizeof(int) * temperaturesSize);
    int top = -1;  // 栈顶指针,-1表示栈为空
    
    // 3. 遍历每个温度,分情况处理栈顶元素与当前元素的关系
    for (int i = 0; i < temperaturesSize; i++) {
        // 当前元素:temperatures[i],当前索引:i
        int current_temp = temperatures[i];
        
        // 栈不为空时,才需要比较栈顶元素
        while (top != -1) {
            int stack_top_idx = stack[top];  // 栈顶索引
            int stack_top_temp = temperatures[stack_top_idx];  // 栈顶对应的温度
            
            // 情况1:当前温度 > 栈顶温度 → 栈顶元素找到下一个更高温度
            if (current_temp > stack_top_temp) {
                answer[stack_top_idx] = i - stack_top_idx;  // 计算天数差
                top--;  // 弹出栈顶(已处理完)
            }
            // 情况2:当前温度 == 栈顶温度 → 栈顶元素未找到更高温度,不弹出
            else if (current_temp == stack_top_temp) {
                break;  // 停止弹出,当前温度不满足"更高",无需继续检查
            }
            // 情况3:当前温度 < 栈顶温度 → 栈顶元素未找到更高温度,不弹出
            else {  // current_temp < stack_top_temp
                break;  // 停止弹出,当前温度更小,无需继续检查
            }
        }
        
        // 无论哪种情况,当前索引都要入栈(等待后续更高温度)
        top++;
        stack[top] = i;
    }
    
    // 4. 释放资源并返回结果
    free(stack);
    return answer;
}

我的乱乱的思路:

c语言不是很会写,元素入栈,单调,然后数组索引其实就是有无高温天气,不是很难,主要是不会写,数组索引要处理一下,然后和原来的数组对应。

原数组,进栈出栈得到单调栈,温度天数索引数组,不会写,也不知道这个怎么实现,感觉理论上有这种下一个最大或者最小的算法题就是单调栈,但不会写具体栈和数组的实现,语法也不熟悉。


还得是代码随想录讲的,豁然开朗了!

代码随想录讲解传送门

【单调栈,你该了解的,这里都讲了!LeetCode:739.每日温度】


按照代码随想录分三种情况处理的解法

虽然冗余但是好理解。

为了让思路更清晰,我们明确区分栈顶元素与当前元素的三种关系(大于、等于、小于),并在代码中对应处理。核心逻辑依然是单调栈,但会把每种情况的判断写得更明确。

完整代码(分情况处理)
cpp 复制代码
/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
int* dailyTemperatures(int* temperatures, int temperaturesSize, int* returnSize) {
    // 1. 初始化返回结果相关
    *returnSize = temperaturesSize;  // 设置返回数组的大小
    int* answer = (int*)malloc(sizeof(int) * temperaturesSize);
    for (int i = 0; i < temperaturesSize; i++) {
        answer[i] = 0;  // 默认值为0(没有更高温度)
    }
    
    // 2. 初始化单调栈(存储温度的索引)
    int* stack = (int*)malloc(sizeof(int) * temperaturesSize);
    int top = -1;  // 栈顶指针,-1表示栈为空
    
    // 3. 遍历每个温度,分情况处理栈顶元素与当前元素的关系
    for (int i = 0; i < temperaturesSize; i++) {
        // 当前元素:temperatures[i],当前索引:i
        int current_temp = temperatures[i];
        
        // 栈不为空时,才需要比较栈顶元素
        while (top != -1) {
            int stack_top_idx = stack[top];  // 栈顶索引
            int stack_top_temp = temperatures[stack_top_idx];  // 栈顶对应的温度
            
            // 情况1:当前温度 > 栈顶温度 → 栈顶元素找到下一个更高温度
            if (current_temp > stack_top_temp) {
                answer[stack_top_idx] = i - stack_top_idx;  // 计算天数差
                top--;  // 弹出栈顶(已处理完)
            }
            // 情况2:当前温度 == 栈顶温度 → 栈顶元素未找到更高温度,不弹出
            else if (current_temp == stack_top_temp) {
                break;  // 停止弹出,当前温度不满足"更高",无需继续检查
            }
            // 情况3:当前温度 < 栈顶温度 → 栈顶元素未找到更高温度,不弹出
            else {  // current_temp < stack_top_temp
                break;  // 停止弹出,当前温度更小,无需继续检查
            }
        }
        
        // 无论哪种情况,当前索引都要入栈(等待后续更高温度)
        top++;
        stack[top] = i;
    }
    
    // 4. 释放资源并返回结果
    free(stack);
    return answer;
}

逐情况详细讲解

核心逻辑回顾

单调栈的作用是暂存 "尚未找到下一个更高温度" 的天数索引 ,栈内索引对应的温度保持单调递减(从栈底到栈顶温度越来越小)。遍历每个温度时,通过比较当前温度与栈顶温度的关系,决定是否处理栈顶元素。

三种情况的处理逻辑
1. 情况 1:当前温度 > 栈顶温度(current_temp > stack_top_temp
  • 含义:栈顶索引对应的那一天,终于找到了 "下一个更高温度"(就是当前天)。

  • 处理

    • 计算天数差:当前索引 i - 栈顶索引 stack_top_idx,存入 answer[stack_top_idx]
    • 弹出栈顶元素(top--),因为它已经找到结果,不需要再留在栈里。
    • 继续循环检查新的栈顶元素(可能当前温度也比新栈顶高,需要继续处理)。

    举例 :栈顶是索引 4(温度 69),当前是索引 5(温度 72)。72 > 69,所以 answer[4] = 5-4=1,弹出索引 4,再检查新栈顶(索引 3,温度 71),72 > 71,继续处理。

2. 情况 2:当前温度 == 栈顶温度(current_temp == stack_top_temp
  • 含义:当前温度与栈顶温度相等,不满足 "更高" 的条件,栈顶元素还没找到结果。

  • 处理

    • 停止循环(break),不弹出栈顶元素。
    • 因为即使继续检查栈内其他元素,当前温度也不满足 "更高",无需再比较。

    举例:栈顶是索引 2(温度 75),当前是索引 x(温度 75)。75 == 75,栈顶元素(索引 2)的下一个更高温度不是当前天,所以不处理,直接退出循环。

3. 情况 3:当前温度 < 栈顶温度(current_temp < stack_top_temp
  • 含义:当前温度比栈顶温度低,栈顶元素的 "下一个更高温度" 肯定不是当前天(可能在更后面)。

  • 处理

    • 停止循环(break),不弹出栈顶元素。
    • 因为栈内元素是单调递减的,栈顶后面的元素温度比栈顶还小,当前温度比栈顶小,自然也比后面的元素小,无需继续比较。

    举例:栈顶是索引 2(温度 75),当前是索引 3(温度 71)。71 < 75,栈顶元素(索引 2)的下一个更高温度不在当前天,所以不处理,退出循环。

最后一步:当前索引入栈

无论上述哪种情况处理完毕后,都要把当前索引 i 入栈(stack[++top] = i)。因为当前天也需要等待 "它自己的下一个更高温度" 出现,所以暂时存到栈里。

为什么这样处理更清晰?

  • 把三种关系(>、==、<)明确拆分,避免了原来 "合并判断" 的简洁写法可能带来的困惑。
  • 每种情况的处理逻辑独立,更容易理解 "为什么栈顶要弹出""为什么要停止循环"。
  • 本质上和精简版本的逻辑一致,但通过显式的条件判断,让初学者能更直观地看到单调栈的维护过程。

总结

这道题的核心是用单调栈 "记住" 还没找到结果的天数索引,通过比较当前温度与栈顶温度的关系,逐步确定每个天数的结果。三种情况的处理都是为了维持栈的 "单调递减" 特性,确保每个元素只需要入栈和出栈一次,从而实现高效的 O (n) 时间复杂度。

ai给的简化解法(升级版)

首先你提到 "用单调栈,处理数组索引,找到下一个更高温度的天数",这个思路是完全正确的!

不过可能对 "栈怎么存索引""怎么通过索引计算天数" 这些细节有疑问,我们慢慢理清楚。

一、先理解题目要做什么

给一个温度数组,比如 [73,74,75,71,69,72,76,73],我们要返回一个数组 answer,其中 answer[i] 表示:第 i 天之后,过几天会出现比 temperatures[i] 更高的温度。如果一直没有,就填 0。

比如第 0 天温度是 73,下一个更高的是第 1 天的 74,所以 answer[0] = 1-0 = 1;第 2 天温度是 75,下一个更高的是第 6 天的 76,所以 answer[2] = 6-2 = 4

二、为什么用 "单调栈"?

你已经想到用单调栈,这非常棒!因为我们要找 "每个元素后面第一个比它大的元素",这种 "找下一个更大元素" 的问题,单调栈是最高效的解法(时间复杂度 O (n))。

如果不用单调栈,可能会想到 "暴力解法":对每个 i,往后遍历找第一个比 temperatures[i] 大的元素,记录天数。但这样时间复杂度是 O (n²),如果数组很长(比如 10^5 个元素),会超时。所以单调栈是最优选择。

三、代码逐行拆解(结合你的思路)

我们就按你提供的代码,一步一步看每句代码的作用,以及为什么这么写。

1. 初始化返回值相关
cpp 复制代码
*returnSize = temperaturesSize;  // 设置返回数组的大小
int* answer = (int*)malloc(sizeof(int) * temperaturesSize);  // 申请结果数组的内存
for (int i = 0; i < temperaturesSize; i++) {
    answer[i] = 0;  // 初始化结果数组为0(默认没有更高温度)
}
  • 为什么要初始化 answer 为 0?因为如果某个温度后面一直没有更高的,结果就是 0,提前初始化可以省去后续处理。
  • malloc 是申请动态内存,因为函数返回的数组需要在函数外释放,所以必须用 malloc 而不是局部数组。
2. 初始化单调栈
cpp 复制代码
int* stack = (int*)malloc(sizeof(int) * temperaturesSize);  // 栈的内存
int top = -1;  // 栈顶指针,-1表示栈为空
  • 这里的栈存的是 "温度数组的索引"(比如 0、1、2...),而不是温度值本身。这是你思路的核心,非常关键!因为我们需要用索引计算 "天数差"(比如第 i 天和第 j 天的差是 j-i)。
  • 为什么栈的大小是 temperaturesSize?因为最坏情况下,所有元素都会进栈(比如温度一直递减,如 [5,4,3,2,1]),所以最大需要这么多空间。
  • top = -1 是栈的常用初始化方式:top 表示栈顶的位置,-1 说明栈里没有元素。
3. 遍历温度数组(核心逻辑)
cpp 复制代码
for (int i = 0; i < temperaturesSize; i++) {  // i 是当前天的索引
    // 当栈不为空,且当前温度 > 栈顶索引对应的温度时
    while (top != -1 && temperatures[i] > temperatures[stack[top]]) {
        int prev_idx = stack[top--];  // 弹出栈顶索引(之前的某一天)
        answer[prev_idx] = i - prev_idx;  // 计算天数差
    }
    // 当前索引入栈
    stack[++top] = i;
}

这部分是最关键的,我们结合例子(temperatures = [73,74,75,71,69,72,76,73])一步步模拟,你就明白了。

步骤 1:i=0(当前天是第 0 天,温度 73)
  • 栈是空的(top=-1),所以不进入 while 循环。
  • 执行 stack[++top] = itop 从 -1 变成 0,栈里存 [0](表示第 0 天的索引)。
步骤 2:i=1(当前天是第 1 天,温度 74)
  • 栈不为空(top=0),栈顶索引是 0,对应的温度是 73。当前温度 74 > 73,满足 while 条件。
    • 弹出栈顶:prev_idx = stack[0] = 0top 变回 -1。
    • 计算天数差:answer[0] = 1 - 0 = 1(第 0 天的结果确定了)。
  • while 循环结束(栈空了),执行 stack[++top] = 1:栈里存 [1]
步骤 3:i=2(当前天是第 2 天,温度 75)
  • 栈顶索引是 1,对应温度 74。当前温度 75 > 74,进入 while
    • 弹出栈顶:prev_idx=1top=-1
    • 计算:answer[1] = 2-1=1(第 1 天的结果确定了)。
  • 栈空,执行 stack[++top] = 2:栈里存 [2]
步骤 4:i=3(当前天是第 3 天,温度 71)
  • 栈顶索引是 2,对应温度 75。当前温度 71 < 75,不满足 while 条件。
  • 执行 stack[++top] = 3:栈里存 [2,3]
步骤 5:i=4(当前天是第 4 天,温度 69)
  • 栈顶索引是 3,对应温度 71。69 < 71,不进入 while
  • 入栈:栈里存 [2,3,4]
步骤 6:i=5(当前天是第 5 天,温度 72)
  • 栈顶索引是 4,对应温度 69。72 > 69,进入 while
    • 弹出 4,answer[4] = 5-4=1(第 4 天结果确定)。栈现在是 [2,3]
  • 栈顶索引是 3,对应温度 71。72 > 71,继续 while
    • 弹出 3,answer[3] =5-3=2(第 3 天结果确定)。栈现在是 [2]
  • 栈顶索引是 2,对应温度 75。72 < 75,while 结束。
  • 入栈:栈里存 [2,5]
步骤 7:i=6(当前天是第 6 天,温度 76)
  • 栈顶索引是 5,对应温度 72。76 >72,进入 while
    • 弹出 5,answer[5] =6-5=1(第 5 天结果确定)。栈现在是 [2]
  • 栈顶索引是 2,对应温度 75。76>75,继续 while
    • 弹出 2,answer[2] =6-2=4(第 2 天结果确定)。栈空了。
  • while 结束,入栈:栈里存 [6]
步骤 8:i=7(当前天是第 7 天,温度 73)
  • 栈顶索引是 6,对应温度 76。73 <76,不进入 while
  • 入栈:栈里存 [6,7]

遍历结束后,栈里剩下的 6 和 7,它们对应的 answer 还是初始的 0(因为后面没有更高温度了)。最终 answer 就是 [1,1,4,2,1,1,0,0],和示例一致。

4. 释放内存并返回
cpp 复制代码
free(stack);  // 释放栈的内存(栈只是临时用的,不用返回)
return answer;  // 返回结果数组
  • 为什么要释放 stack?因为 stack 是在函数内 malloc 的,不用了就要释放,避免内存泄漏。
  • answer 不能在这里释放,因为要返回给调用者使用,调用者会负责释放。

四、可能的误区(结合你的思路)

你提到 "数组索引要处理一下,然后和原来的数组对应",这其实是核心点,但初学者可能会有这些疑问:

  1. **为什么栈存索引而不是温度值?**因为我们需要计算 "天数差"(比如第 i 天和第 j 天的差是 j-i),如果存温度值,就不知道它是哪一天的了,无法计算差。所以存索引才能关联到具体的天数。

  2. **为什么栈要保持 "单调递减"?**栈里的索引对应的温度是越来越小的(从栈底到栈顶)。这样当遇到一个更高的温度时,栈顶的索引对应的温度一定是目前最小的,只要当前温度比它大,就能确定栈顶索引的 "下一个更高温度" 就是当前天。如果不保持递减,就需要多次回溯,效率变低。

  3. **为什么 while 循环要用 "弹出栈顶" 的操作?**因为当前温度可能比栈里多个元素都高。比如当前温度是 76,栈里有 [2,5](对应温度 75、72),76 比 72 大,处理完 5 之后,还要检查 2(75),因为 76 也比 75 大,所以需要循环弹出,直到栈顶温度不小于当前温度。

五、总结

这道题的核心就是用 "单调栈存索引",通过维护栈的 "单调递减" 特性,高效找到每个温度的 "下一个更高温度"。步骤可以简化为:

  1. 遍历每个温度(当前天 i)。
  2. 只要栈里有元素,且当前温度比栈顶索引的温度高,就弹出栈顶,计算天数差(i - 栈顶索引),存到结果数组。
  3. 把当前天的索引入栈。
  4. 遍历结束后,结果数组中没被修改的位置就是 0(默认值)。

这样每个元素最多入栈和出栈一次,时间效率很高,适合处理大规模数据。多手动模拟几遍栈的变化,就能完全掌握了!

相关推荐
jinmo_C++4 小时前
数据结构_深入理解堆(大根堆 小根堆)与优先队列:从理论到手撕实现
java·数据结构·算法
im_AMBER5 小时前
React 05
开发语言·前端·javascript·笔记·学习·react.js·前端框架
IT19955 小时前
OpenSSL3.5.2实现SM3数据摘要生成
算法·哈希算法·散列表
Excuse_lighttime5 小时前
排序数组(快速排序算法)
java·数据结构·算法·leetcode·eclipse·排序算法
潘小安5 小时前
『译』迄今为止最强的 RAG 技术?Anthropic 的上下文检索与混合搜索
算法·llm·claude
kessy15 小时前
安全与续航兼备的“国密芯”——LKT6810U
算法
leo__5206 小时前
基于经验模态分解的去趋势波动分析(EMD-DFA)方法
人工智能·算法·机器学习
lzptouch6 小时前
AdaBoost(Adaptive Boosting)算法
算法·集成学习·boosting
南方的狮子先生6 小时前
【数据结构】(C++数据结构)查找算法与排序算法详解
数据结构·c++·学习·算法·排序算法·1024程序员节