题目描述:
给定一个整数数组 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 <= 10^530 <= temperatures[i] <= 100
思路:
有点麻爪呢一开始。动态规划?i-1和i好像没啥状态转移的关系。不能用。
栈?这个题该用栈我倒是知道。
解决第一个问题:栈里存什么。2,什么情况该入栈,3什么情况该出栈。
经过一个例子的思考,我想到了:
temperatures = [73,74,75,71,69,72,76,73]
- 栈里放的是数组下标。
- 遍历到谁,谁就入栈了
- 新检查的数字比栈顶大,栈顶就出栈,但是没完,只要比栈顶大就继续出栈,直到栈空或者没有栈顶大,就入栈。
- 循环知道数组结束
能想到用栈是关键,怎么用栈就相对简单了。
实现:
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
Deque<Integer> stack = new LinkedList<>();
int[] answer = new int[temperatures.length];
for (int i = 0 ; i < temperatures.length; i++) {
if (!stack.isEmpty()) {
int topIndex = stack.peek();
while (!stack.isEmpty() && temperatures[i] > temperatures[topIndex]) {
stack.pop();
answer[topIndex] = i - topIndex;
topIndex = stack.isEmpty() ? 0 : stack.peek();
}
}
stack.push(i);
}
return answer;
}
}
实现起来基本没有难度,逻辑分支也不多。只要注意处理好空栈的情况就可以了。但是我觉得还有优化的空间。
DS的优化建议:
根据你的代码和常见的优化策略,以下是一些优化建议及分析(基于当前实现的时间复杂度O(n)已经是最优解,因此优化重点在于代码结构和执行效率):
一、数据结构优化
- 改用ArrayDeque替代LinkedList
当前使用的LinkedList虽然实现了Deque接口,但ArrayDeque在栈操作中性能更优: • 内存连续访问,缓存命中率高(空间局部性原理) • 无需维护链表节点的额外内存开销Deque<Integer> stack = new ArrayDeque<>(temperatures.length); // 预分配容量
二、循环逻辑优化
- 移除冗余的栈空判断
外层if (!stack.isEmpty())与内层while条件存在重复判断,可合并逻辑:(原始代码中while (!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) { int topIndex = stack.pop(); answer[topIndex] = i - topIndex; }topIndex的三元运算符存在潜在风险,当栈空时peek()会抛出异常,但合并后逻辑更安全)
三、预分配与内存优化
- 数组初始化优化
Java默认会将int数组初始化为0,无需显式赋值:int[] answer = new int[temperatures.length]; // 默认值0符合题目要求
四、代码可读性提升
- 变量命名与注释增强
添加关键注释说明单调栈的特性:// 维护单调递减栈(栈底到栈顶温度递减) Deque<Integer> stack = new ArrayDeque<>();
优化后完整代码
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
Deque<Integer> stack = new ArrayDeque<>(temperatures.length); // 预分配容量
int[] answer = new int[temperatures.length];
for (int i = 0; i < temperatures.length; i++) {
// 破坏单调性时弹出并计算天数差
while (!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) {
int topIndex = stack.pop();
answer[topIndex] = i - topIndex;
}
stack.push(i); // 保持单调递减特性
}
return answer;
}
}
性能对比分析
| 优化项 | LinkedList版本 (ns/op) | ArrayDeque版本 (ns/op) | 提升幅度 |
|---|---|---|---|
| 10^4次push/pop操作 | 3520 | 2410 | 31.5% |
| 内存占用 (1e6元素) | 64MB | 32MB | 50% |
(数据参考自JVM基准测试工具JMH,硬件环境:Intel i7-11800H)
五、进阶优化思路(针对特大数据)
若需处理超过1e7量级的数据,可考虑:
- 反向遍历法:利用温度值范围有限(30-100)的特性,记录每个温度最后出现的位置
- 并行计算:将数组分段处理(需注意栈操作的线程安全问题)
优化后的代码在LeetCode测试集中运行时间从8ms降低到6ms,内存消耗从51MB降低到49.8MB。该算法已是最优时间复杂度(O(n)),进一步优化需结合具体场景调整策略。
听从DS的优化建议之后的版本:
看了DS的建议,果然我就说有可以优化的地方。比如topIndex的处理,多余的if等等。
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
Deque<Integer> stack = new ArrayDeque<>();
int[] answer = new int[temperatures.length];
for (int i = 0 ; i < temperatures.length; i++) {
while (!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) {
int topIndex = stack.pop();
answer[topIndex] = i - topIndex;
}
stack.push(i);
}
return answer;
}
}
确实优化后的代码更易读,更容易维护。