这是我参与8月更文挑战的第25天,活动详情查看:8月更文挑战
每日温度(题号739)
题目
请根据每日 气温 列表 temperatures,请计算在每一天需要等几天才会有更高的温度。如果气温在这之后都不会升高,请在该位置用 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 <= 10530 <= temperatures[i] <= 100
链接
解释
这题啊,这题是旧菜新吃。
看题目是不是觉得眼熟,有点那味儿了。
确实,好像之前写过这题,但讲真,不记得是哪一题了,反正身上有那题的影子。
先不说这个了,想想正常的解法。
正常人看到这题一般都会用两层循环来解决问题,第一层循环遍历每个元素,第二层循环从当前元素开始向后遍历,累计比当前元素小的元素,遇到比当前元素大的元素,将累计的个数推入res数组中,这没啥可说的。
重点是另外一种解法,笔者想了半天也没想出来是哪一题,但感觉这题应该用栈来解。
一个模糊的想法就是用栈来记录元素的index,然后根据index代表元素的大小关系来进行推入推出操作,籍此获取到当前元素后面最近的大于当前元素的位置,然后俩位置一减,结果就出来了。
后续仔细一想,原来可以这么操作,具体步骤👇:
- 新建一个栈,初始化值为空
- 开始循环原始数组:
- 判断栈里是否有元素
- 如果有元素,判断栈顶的元素是否小于等于当前元素,如果小于等于,那么去掉栈顶元素,利用
while进行一个持续的操作 - 如果没有元素,啥也不干
- 如果有元素,判断栈顶的元素是否小于等于当前元素,如果小于等于,那么去掉栈顶元素,利用
- 判断栈内是否有元素
- 如果有元素,利用栈顶元素的值减去当前元素的
index,取差值后推入res的头部 - 如果没有元素,向
res的头部推入0
- 如果有元素,利用栈顶元素的值减去当前元素的
- 向栈顶推入当前
index
- 判断栈里是否有元素
注意,这里的循环顺序是从后往前,而不是从前往后。
为什么要这么做?
原因很简单,因为题目是统计后面较大元素距离当前元素的位置,所以得从后往前数。
那为什么要这么操作呢?
首先,这里维护了一个栈,在JavaScript中其实就是一数组,每次循环开始之前,从前往后看看栈中是否有元素小于当前元素,去掉这些小于等于的元素,那么此时数组的第一个元素就一定比当前元素的值更大,那么利用数组第一个元素的index减去当前元素的index就可以得到当前元素后最近的较大元素的位置差值,也就是题目让我们统计的个数了。
最后别忘了将当前元素的index推入数组的头节点,记录下当前元素,方面后续的遍历。
整体的想法就是这样,如果有做过类似题目的会比较容易想到,如果是第一次看到可能会比较困难,问题不大,慢慢来嘛~
自己的答案(双循环)
var dailyTemperatures = function(temperatures) {
const res = []
for (let i = 0; i < temperatures.length; i++) {
let count = 0
let flag = false
for (let j = i + 1; j < temperatures.length; j++) {
if (temperatures[j] > temperatures[i]) {
count++
res.push(count)
flag = true
break;
} else {
count++
}
}
if (!flag) res.push(0)
}
return res
};
双循环就没啥可以说的了,简单就完事了,为了正确的插入0,需要利用一个flag值来对当前状态进行一个记录,其他就是正常思维。
顺序也是从前到后,比较好理解,时间复杂度为O(nlogn),空间复杂度为O(n),也就是我们的res,如果不算res就是O(1)了。
自己的答案(栈)
关于栈这块就有点可值得思考的地方了,根据解释中的介绍,写出来的代码应该是这样的👇:
var dailyTemperatures = function(temperatures) {
const res = [0]
const len = temperatures.length
const stack = [len - 1]
for (let i = temperatures.length - 2; i > -1; i--) {
while (temperatures[i] >= temperatures[stack[0]] && stack.length) {
stack.shift()
}
res.unshift(stack.length ? stack[0] - i : 0)
stack.unshift(i)
}
return res
};
为了维护res的顺序和stack的顺序,用的是shift和unshift,但耗时就是不如其它人的解法,这就很令人费解了,二者能差个40%左右吧,十分令人迷惑。
后续笔者开始了经典的控制变量法,一点点修改代码之间的区别,后来发现了问题所在,其实思路和解法上没有什么区别,重点在于细节部分,笔者是利用unshift一点点往res的头部推入元素,而性能更好的解法是在一开始就给res规定了长度和默认值,遇到有非0的结果才利用index修改res的元素内容。
这一方面避免了赋值0的操作,一方面利用index直接修改元素值会比unshift快些,具体笔者也没有进行测试,根据猜测应该是这两种可能。
所以优化后的代码是酱婶的👇:
var dailyTemperatures = function(temperatures) {
const len = temperatures.length
const res = new Array(len).fill(0)
const stack = [len - 1]
for (let i = temperatures.length - 2; i > -1; i--) {
while (temperatures[i] >= temperatures[stack[stack.length - 1]] && stack.length) {
stack.pop()
}
if (stack.length) res[i] = stack[stack.length - 1] - i
stack.push(i)
}
return res
};
顺便还用pop和push替代了shift和unshift,理解起来更简单些。
更好的方法(KMP)
说到KMP笔者是真的不是很理解,这方法是笔者看到评论区有个老哥发出来的,他把空间复杂度优化到O(1),笔者觉得不错,就python的代码改写成了JavaScript👇:
var dailyTemperatures = function(temperatures) {
const n = temperatures.length
const ans = new Array(n).fill(0)
for (let i = n - 2; i > -1; i--) {
let now = i + 1
let flag = false
while (temperatures[now] <= temperatures[i]) {
if (ans[now]) {
now += ans[now]
} else {
flag = true
break
}
}
if(!flag) ans[i] = now - i
}
return ans
};
由于JavaScript和python的语法有所不同,所以没办法完美的改写,有更好方案的老哥可以在评论区留言,大家一起讨论讨论~
PS:想查看往期文章和题目可以点击下面的链接:
这里是按照日期分类的👇
经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇