Leetcode 739 每日温度题解
[toc]
题目
请根据每日
气温
列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用0
来代替。来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/daily-temperatures/
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
示例
例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。
提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。
思路与分析
方法1 暴力求解
气温列表的长度为不超过30000,暴力解法相当于对于每个温度都从左往右遍历找到第一个温度大于当前温度的下标,因此时间复杂度为O(n^2)。在题目条件下是可以通过的。暴力解法的思路比较简单,在这不做过多的赘述。
方法2 单调栈
分析暴力解法可以优化的地方。通过对于示例进行简单的分析,我们可以发现,在找寻比温度75大的下一个温度的时候,71,69,72的目标温度结点也是可以一次得出的。因此,暴力解法的问题在于对于某些结点进行了重复的计算。因此,优化的方向就是利用已经计算出的结果减少不必要的搜索(剪枝)或者减少重复的计算。对于剪枝,将在方法三中进行解释。对于如何减少重复的计算,通过前面的分析我们可以知道,当后面的温度先有结果时,应该直接保存后面的结果。这种后面的温度先处理的思想正好与栈的作用一致。
因此,在这引出了单调栈的概念。单调栈的含义,顾名思义,就是指栈中的元素是单调的。
例如,递减栈指的就是栈中的元素是单调递减的,例如75,71,69。当我们遇到一个温度大于此前的某个温度的时候,我们已经找到了某个结果。换言之,如果我们使用递减栈来处理温度。则当当前温度大于栈顶的温度时,栈中至少有一个元素找到了结果。为了保证栈是递减的,我们将栈中温度小于当前温度的栈都弹出,并保存结果,然后将当前温度入栈。当所有温度遍历完成后,我们就得到了结果。
注意,因为题目要求的是位置(坐标),因此压入栈中的元素实际上是温度的索引。索引的差值就是下一个温度升高的天数。
代码
class Solution {
public int[] dailyTemperatures(int[] T) {
int n = T.length;
//用于保存结果
int[] ans = new int[n];
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < n; i++) {
//若栈不为空且当前温度大于栈顶温度,说明栈顶温度的结果已经找到,
//将栈顶温度弹出并保存结果
while (!stack.empty() && T[i] > T[stack.peek()]) {
ans[stack.peek()] = i - stack.peek();
stack.pop();
}
stack.push(i);
}
return ans;
}
}
时间复杂度O(n),每个温度遍历一次。
空间复杂度O(n),用于单调栈,最坏情况下栈的长度为n。
方法3 剪枝
通过分析前面的示例,我们注意到中间有重复的计算过程。更进一步,中间的重复计算是由于从左往右的顺序造成的。例如在计算温度75的结果时,我们需要遍历71,69,72,76。而在这个过程中,71,69,72的结果其实已经知道了。但当我们计算71的结果时,我们仍需访问69,72。那么,如果我们从右往左计算呢?
我们可以发现,最后一个温度的结果肯定是0,倒数第二个温度的结果取决于它右边的温度(即倒数第一个温度)。倒数第三个温度的结果取决于倒数第一,第二个温度......。而右边温度的结果我们是已知的,因此,我们可以利用这个结果来进行剪枝。举个例子,76的下一个温度是72,72小于76,而72的结果是0,表示右边没有比72大的温度,因此可以提前结束搜索,76的结果一定是0。72的下一个温度是76,则72的结果为1。75的下一个温度是71,小于75,下一个比71大的温度是71的结果72,小于75,下一个比72大的是72的结果76,大于75.因此75的结果就是76的下标减去75的下标。
综上所述,在处理当前温度时有三种情况。
- 下一个温度大于当前温度,计算结果。.
- 下一个温度的结果为0, 结果为0(因为右边以及没有比下一个温度大的温度)
- 将比较对象移到下一个温度的结果,重复第一步。
代码
class Solution {
public int[] dailyTemperatures(int[] T) {
int n = T.length;
int[] ans = new int[n];
//倒数第一个温度的结果为0,直接从倒数第二个结点搜索
for (int i = n - 2; i >= 0; i--) {
//从下一个结点开始查找
int j = i + 1;
while (j < n) {
if (T[j] > T[i]) {
//找到结果,计算结果并提前结束当前结点的计算
ans[i] = j - i;
break;
} else if (ans[j] == 0) {
//表明右边已经不可能有大于当前温度的温度,直接结束搜索。
break;
} else {
//看下一个更大的温度是否是结果
j += ans[j];
}
}
}
return ans;
}
}