一、题目描述
给定一个整数数组 temperatures,表示每天的温度,返回一个数组 answer,其中 answeri 是指对于第 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 <= 10^5
- 30 <= temperaturesi <= 100
二、解题思路总览
拿到这道题,首先思考:如何找到每个元素"右边第一个比它大的元素"?
直观的暴力解法是 O(n^2):
- 对于每个元素,向右遍历找到第一个更大的
- 最坏情况 O(n2),105 数据会超时
关键洞察: 想象一下排队买饭,如果你是倒数第一个人,后面没人了,你的等待天数是 0。但如果前面有个比你高的人,你就不用一直往后找了。
核心思路: 利用单调递减栈(从栈底到栈顶,温度单调递减)。
| 步骤 | 思路 | 说明 |
|---|---|---|
| 1 | 从右往左遍历 | 右侧先处理完,左侧才能确定答案 |
| 2 | 维护单调递减栈 | 栈中存索引,温度依次递减 |
| 3 | 弹栈找更大元素 | 弹出比当前温度小的,遇到更大的停止 |
| 4 | 计算距离 | ansi = 栈顶索引 - i |
为什么从右往左遍历?
| 对比 | 从左往右遍历 | 从右往左遍历 |
|---|---|---|
| 处理顺序 | 前面不知道后面 | 后面已经处理完,前面可以直接用 |
| 栈维护 | 需要记录"等待更高温的天数" | 栈顶就是答案,直接计算 |
三、完整代码(方法一:单调递减栈)
cpp
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
int n = temperatures.size();
vector<int> ans(n);
stack<int> st; // 存索引,温度单调递减
for (int i = n - 1; i >= 0; i--) {
int t = temperatures[i];
// 弹栈:栈顶温度 <= 当前温度,说明栈顶不是答案
while (!st.empty() && t >= temperatures[st.top()]) {
st.pop();
}
// 如果栈不为空,栈顶就是右边第一个更大元素
if (!st.empty()) {
ans[i] = st.top() - i;
}
// 当前索引入栈
st.push(i);
}
return ans;
}
};
四、其他解法
方法二:从左往右遍历(维护"等待表")
cpp
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
int n = temperatures.size();
vector<int> ans(n, 0);
stack<int> st; // 存等待更高温的索引,温度单调递增
for (int i = 0; i < n; i++) {
while (!st.empty() && temperatures[i] > temperatures[st.top()]) {
// 当前温度比栈顶的等待者更高,栈顶的等待结束
ans[st.top()] = i - st.top();
st.pop();
}
st.push(i); // 当前索引入栈等待更高温
}
return ans;
}
};
思路差异: 从左往右时,栈中存的是"等待更高温的索引",遇到更高温度时直接结算答案。
方法三:暴力解法(O(n^2),仅作对比)
cpp
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
int n = temperatures.size();
vector<int> ans(n, 0);
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (temperatures[j] > temperatures[i]) {
ans[i] = j - i;
break;
}
}
}
return ans;
}
};
问题: 两层循环,最坏 O(n2),105 数据会超时。仅作对比,展示单调栈优化的必要性。
方法四:预处理 + 二分查找(不推荐)
cpp
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
int n = temperatures.size();
vector<int> ans(n, 0);
vector<int> sortedIdx(n);
iota(sortedIdx.begin(), sortedIdx.end(), 0);
sort(sortedIdx.begin(), sortedIdx.end(),
[&](int a, int b) { return temperatures[a] < temperatures[b]; });
for (int i = 0; i < n; i++) {
// 二分查找 temperatures[i] 的下一个更大元素
//省略实现...二分本身也是 O(log n),但整体更复杂
}
return ans;
}
};
问题: 复杂度和实现难度都高于单调栈,不推荐。
五、算法流程图
以输入 temperatures = [73,74,75,71,69,72,76,73] 为例,逐步展示算法执行过程:
初始状态:
temps: [73, 74, 75, 71, 69, 72, 76, 73]
indices: 0 1 2 3 4 5 6 7
ans: [0, 0, 0, 0, 0, 0, 0, 0]
st: [空]
Step 1: i=7, t=73
- st空,ans[7]=0(默认)
- st.push(7)
temps: [73, 74, 75, 71, 69, 72, 76, 73]
^
st: [7] (温度: 73)
Step 2: i=6, t=76
- t(76) >= temps[st.top()](73),弹出7
- st空,ans[6]=0(默认)
- st.push(6)
temps: [73, 74, 75, 71, 69, 72, 76, 73]
^
st: [6] (温度: 76)
Step 3: i=5, t=72
- t(72) >= temps[st.top()](76)? 否,停止弹栈
- ans[5] = st.top() - 5 = 6 - 5 = 1
- st.push(5)
temps: [73, 74, 75, 71, 69, 72, 76, 73]
^
st: [6, 5] (温度: 76, 72)
Step 4: i=4, t=69
- t(69) >= temps[5](72)? 否,停止弹栈
- ans[4] = st.top() - 4 = 5 - 4 = 1
- st.push(4)
temps: [73, 74, 75, 71, 69, 72, 76, 73]
^
st: [6, 5, 4] (温度: 76, 72, 69)
Step 5: i=3, t=71
- t(71) >= temps[4](69)? 是,弹出 4
- t(71) >= temps[5](72)? 否,停止弹栈
- ans[3] = st.top() - 3 = 5 - 3 = 2
- st.push(3)
temps: [73, 74, 75, 71, 69, 72, 76, 73]
^
st: [6, 5, 3] (温度: 76, 72, 71)
Step 6: i=2, t=75
- t(75) >= temps[3](71)? 是,弹出 3
- t(75) >= temps[5](72)? 是,弹出 5
- t(75) >= temps[6](76)? 否,停止弹栈
- ans[2] = st.top() - 2 = 6 - 2 = 4
- st.push(2)
temps: [73, 74, 75, 71, 69, 72, 76, 73]
^
st: [6, 2] (温度: 76, 75)
Step 7: i=1, t=74
- t(74) >= temps[2](75)? 否,停止弹栈
- ans[1] = st.top() - 1 = 2 - 1 = 1
- st.push(1)
temps: [73, 74, 75, 71, 69, 72, 76, 73]
^
st: [6, 2, 1] (温度: 76, 75, 74)
Step 8: i=0, t=73
- t(73) >= temps[1](74)? 否,停止弹栈
- ans[0] = st.top() - 0 = 1 - 0 = 1
- st.push(0)
temps: [73, 74, 75, 71, 69, 72, 76, 73]
^
st: [6, 2, 1, 0] (温度: 76, 75, 74, 73)
最终结果:
ans = [1, 1, 4, 2, 1, 1, 0, 0] ✓
六、逐行解析(方法一)
cpp
vector<int> ans(n);
stack<int> st;
初始化: ans 预设为0(表示没有更高温),st 存索引(便于计算距离)。
cpp
for (int i = n - 1; i >= 0; i--) {
逆序遍历: 从右往左处理,这样栈顶始终是"当前元素右边第一个更大元素"的位置索引。
cpp
int t = temperatures[i];
while (!st.empty() && t >= temperatures[st.top()]) {
st.pop();
}
弹栈逻辑: 当前温度 t 如果大于等于栈顶温度对应的元素,说明栈顶元素找到了更低温(或等于的温度),它不再是答案,弹出。继续弹直到栈顶元素温度更高。
cpp
if (!st.empty()) {
ans[i] = st.top() - i;
}
计算答案: 栈顶索引减去当前索引,就是等待天数。
cpp
st.push(i);
}
入栈: 当前索引入栈,它可能成为左边元素的答案。
七、复杂度分析
各方法复杂度对比
| 方法 | 时间复杂度 | 空间复杂度 | 说明 |
|---|---|---|---|
| 方法一:单调递减栈(逆序) | O(n) | O(n) | 标准解法 |
| 方法二:单调递增栈(正序) | O(n) | O(n) | 思路对称 |
| 方法三:暴力解法 | O(n^2) | O(1) | 不可用,超时 |
| 方法四:预处理+二分 | O(n log n) | O(n) | 不推荐,更复杂 |
核心: 方法一和二是 O(n),因为每个元素最多入栈一次、出栈一次。
方法一详细复杂度分析
时间复杂度:O(n)
- 遍历一次:O(n)
- 每个索引最多入栈一次、出栈一次
- 总弹栈次数 <= 总入栈次数 = O(n)
空间复杂度:O(n)
- 最坏情况:温度单调递增,所有索引都入栈
- 例如:30,31,32,33,...
弹栈分析:
| 情况 | 弹栈次数 | 说明 |
|---|---|---|
| 单调递增温度 | O(n) | 每次都弹出前一个 |
| 单调递减温度 | 0 | 一次都不弹 |
| 随机温度 | 均摊 O(1) | 总弹栈次数 = O(n) |
八、面试追问 FAQ
| 问题 | 回答要点 |
|---|---|
| Q1:为什么要用栈?不用行吗? | 可以用暴力 O(n^2),但会超时。栈的本质是"记录等待更高温的索引",避免重复遍历。 |
| Q2: 从左往右和从右往往哪个更好? | 两种都可以,思路对称。逆序更符合直觉(右边先确定),正序更自然(正常遍历顺序)。 |
| Q3: 如果要找"左边第一个更大元素"怎么办? | 改成正序遍历,栈维护"单调递减"即可。核心是"当前元素需要找的答案在栈顶"。 |
| Q4: 单调栈能解决哪些其他问题? | 柱状图中最大的矩形、接雨水、去除重复字母等。单调栈是处理"下一个更大/更小元素"问题的通用技巧。 |
| Q5: 面试时推荐哪种方法? | 首选方法一(逆序单调递减栈),代码简洁高效。如果面试官问,再补充方法二(正序单调递增栈)。 |
| Q6: 如果温度相等怎么办? | 本题要求"更高温度",相等不算。所以 t >= temperatures[st.top()] 时弹栈是正确的。 |
九、相关题目
| 题号 | 题目 | 难度 | 关联点 |
|---|---|---|---|
| 739 | 每日温度 | 中等 | 本题 |
| 496 | 下一个更大元素 I | 简单 | 单调栈入门 |
| 503 | 下一个更大元素 II | 中等 | 环形数组扩展 |
| 42 | 接雨水 | 困难 | 单调栈综合应用 |
| 84 | 柱状图中最大的矩形 | 困难 | 单调栈综合应用 |
| 316 | 去除重复字母 | 困难 | 单调栈变形 |
推荐刷题顺序:
- 本题(739.每日温度)→ 单调栈入门
- 496(下一个更大元素 I)→ 简单变种
- 503(下一个更大元素 II)→ 环形数组
- 84(柱状图中最大的矩形)→ 综合应用
十、总结
| 维度 | 内容 |
|---|---|
| 考察知识点 | 单调栈、-next Greater Element |
| 难度 | 中等 |
| 核心思维 | 用栈维护"等待更高温的索引",O(1) 找到答案 |
| 关键技巧 | 逆序遍历 + 单调递减栈 |
| 推荐解法 | 方法一(逆序单调递减栈)或方法二(正序单调递增栈) |
| 变形题 | 下一个更小元素、左边第一个更大元素、环形数组 |
一句话总结: 单调栈的核心是"栈顶元素是当前元素要找的答案",维护单调性保证每个元素最多入栈一次、出栈一次,达到 O(n) 时间复杂度。