739. 每日温度
无语了相对简单我还是不会。
日常超时
自己写的暴力算法(超时)
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
vector<int> result;
int i,j;
for(i=0;i<temperatures.size();i++){
int num = 1;
for(j=i+1;j<temperatures.size();j++){
if(temperatures[j]>temperatures[i]){
break;
}
num++;
}
if(j==temperatures.size())num=0;
result.push_back(num);
}
return result;
}
};
官方写的暴力算法
思想:由于温度在30-100这个区间,所以我们用一个数组来存放每个温度第一次出现的下标。
逆序遍历,每次看到一个温度就记录下来。
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
int n = temperatures.size(); //记录温度长度
vector<int> ans(n), next(101, INT_MAX); // ans为最终结果,而next是存放每个温度第一次出现的下标
for (int i = n - 1; i >= 0; --i) { //逆序遍历
int warmerIndex = INT_MAX;
for (int t = temperatures[i] + 1; t <= 100; ++t) { //遍历前面的高温度,看有没有出现过
warmerIndex = min(warmerIndex, next[t]); //这里用min的原因不只是因为INT_MAX,还有一个原因是题目要找的是最先出现的较高温度,当然索引值越低越好啦
}
if (warmerIndex != INT_MAX) { // 如果前面有较高的温度
ans[i] = warmerIndex - i;
}
next[temperatures[i]] = i; //记录当前温度出现的下标
}
return ans;
}
};
这个暴力优于自己写的暴力算法,是他巧妙的把第二个循环限制在低于100次。
突然想想这个解法也不是没有根据的,我自己写的暴力算法中,举个例子,对于[4,3,2,5],我们每次都循环到5,这中间的数字读了许多遍,这其实是冗余的,没有必要的。想到的方法就是把出现过的记录在一个数组中,然后通过索引直接得出。至于为什么要使用逆序,虽然感受到逆序的好处,但是说不上为什么,不过也不需要钻牛角尖,记住这种逆向思维即可。
-
时间复杂度:O(nm),其中 n 是温度列表的长度,m 是数组 next 的长度,在本题中温度不超过 100,所以 m 的值为 100。反向遍历温度列表一遍,对于温度列表中的每个值,都要遍历数组 next 一遍。
-
空间复杂度:O(m),其中 m 是数组 next 的长度。除了返回值以外,需要维护长度为 m 的数组 next 记录每个温度第一次出现的下标位置。
单调栈(正向遍历)
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;
}
};
复杂度分析
for循环是n次,那while循环是多少呢?其实不用关心,因为对于温度列表中的每个下标,最多有一次进栈和出栈的操作。
-
时间复杂度:O(n),其中 n 是温度列表的长度。正向遍历温度列表一遍,对于温度列表中的每个下标,最多有一次进栈和出栈的操作。
-
空间复杂度:O(n),其中 n 是温度列表的长度。需要维护一个单调栈存储温度列表中的下标。
单调栈(反向遍历)
根据官方的正向遍历单调栈代码,自己仿写了反向遍历单调栈代码
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
int n = temperatures.size();
vector<int> ans(n);
stack<int> s;
for (int i = n-1; i >= 0; --i) {
while(!s.empty() && temperatures[i] >= temperatures[s.top()]) {//当看到前面都是更低的温度,出栈
s.pop();
}
s.empty()? ans[i] = 0:ans[i] = s.top() - i;
s.push(i);
}
return ans;
}
};
复杂度分析
同上
关于单调栈
一开始对递增/递减单调栈区别错误了,以为区别就是正反向遍历。
- 单调递增栈:从栈头到栈底数据是从大到小(栈头最大)
- 单调递减栈:从栈头到栈底数据是从小到大(栈头最小)
上面两个单调栈解法,都属于单调递减栈。
关于评论区的精彩解答:
那有同学就问了,我怎么能想到用单调栈呢? 什么时候用单调栈呢?
通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了。
单调栈的本质用空间换时间。