前端刷题路-Day91:每日温度(题号739)

613 阅读3分钟

这是我参与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 <= 105
  • 30 <= temperatures[i] <= 100

链接

leetcode-cn.com/problems/da…

解释

这题啊,这题是旧菜新吃。

看题目是不是觉得眼熟,有点那味儿了。

确实,好像之前写过这题,但讲真,不记得是哪一题了,反正身上有那题的影子。

先不说这个了,想想正常的解法。

正常人看到这题一般都会用两层循环来解决问题,第一层循环遍历每个元素,第二层循环从当前元素开始向后遍历,累计比当前元素小的元素,遇到比当前元素大的元素,将累计的个数推入res数组中,这没啥可说的。

重点是另外一种解法,笔者想了半天也没想出来是哪一题,但感觉这题应该用栈来解。

一个模糊的想法就是用栈来记录元素的index,然后根据index代表元素的大小关系来进行推入推出操作,籍此获取到当前元素后面最近的大于当前元素的位置,然后俩位置一减,结果就出来了。

后续仔细一想,原来可以这么操作,具体步骤👇:

  1. 新建一个栈,初始化值为空
  2. 开始循环原始数组:
    1. 判断栈里是否有元素
      1. 如果有元素,判断栈顶的元素是否小于等于当前元素,如果小于等于,那么去掉栈顶元素,利用while进行一个持续的操作
      2. 如果没有元素,啥也不干
    2. 判断栈内是否有元素
      1. 如果有元素,利用栈顶元素的值减去当前元素的index,取差值后推入res的头部
      2. 如果没有元素,向res的头部推入0
    3. 向栈顶推入当前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的顺序,用的是shiftunshift,但耗时就是不如其它人的解法,这就很令人费解了,二者能差个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
};

顺便还用poppush替代了shiftunshift,理解起来更简单些。

更好的方法(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:想查看往期文章和题目可以点击下面的链接:

这里是按照日期分类的👇

前端刷题路-目录(日期分类)

经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇

前端刷题路-目录(题型分类)