力扣LeetCode 739题:每日温度(单调栈)

题目:

根据每日 气温 列表,请重新生成一个列表,对应位置的输出是需要再等待多久温度才会升高超过该日的天数。如果之后都不会升高,请在该位置用 0 来代替。

例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],
你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。
提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。

分析:题目的意思应该叫做“对于数组的每一个元素,寻找下一个比他大的元素与他的距离”。例如对于 73 下标为 0 ,下一个比他大的元素为 74 下标为 1 ,那么就填入 1-0=1 。以此类推。

一、暴力

直接能够想到的方法就是暴力遍历,对于每一个元素向后遍历:

class Solution {
    public int[] dailyTemperatures(int[] T) {
        int size=T.length;
        int[] ans=new int[size];
        for(int i=0;i<size;i++){
            int j=i;
            while(j<size && T[j]<=T[i]){
                j++;
            }
            //跳出之后没超范围,说明是合法找到
            if(j<size){
                ans[i]=j-i;
            }else{
                ans[i]=0;//否则是溢出而结束
            }
        }
        return ans;
    }
}

时间空间都比较差,最差的情况下,试想,如果温度全部是降序,那么对于每一个元素,都会遍历一整遍到数组末尾,那么时间复杂度是 O(n2)。

二、单调栈

由于之前做过最大矩形面积的题目,用过单调栈这个东西,因此想到了他。

回想一下在示例中:[73, 74, 75, 71, 69, 72, 76, 73]

上一种方法在 75~76 这段,做了很多重复的工作。

实际上当 75 找到比他大的答案是 76 的时候,这两者中间几个元素的答案范围相应缩小了,但是暴力的做法却仍然在盲目进行。所以我们想到用单调栈。

维护一个单调递减的栈,但是不是维护数组的值,而是下标,每次判断用下标到数组里去取值。

知道定义之后,我们选择先来模拟这种方法的步骤,更加方便的计算结果:

  • i=0,入栈: 0
  • i=1,不满足递减,出栈:0 ,1-0=0,得到答案这是 ans[0] ;
  • i=1,入栈: 1
  • i=2,不满足递减,出栈:1,2-1=1,得到答案这是 ans[1];
  • i=2,入栈:2
  • i=3,入栈:2 3
  • i=4,入栈:2 3 4
  • i=5,不满足递减,出栈:4,5-4=1,得到答案这是 ans[4];
  • i=5,不满足递减,出栈:3,5-3=2,得到答案这是ans[3];
  • i=5,入栈:2 5
  • i=6,不满足递减,出栈:5,6-5=1,得到答案这是 ans[5];
  • i=6,不满足递减,出栈:2,6-2=4,得到答案这是 ans[2];
  • i=6,入栈:6
  • i=7,入栈:6 7
  • 数组已经遍历结束,剩下的说明都是递减的,那么都出栈并且记为0,ans[6]=0,ans[7]=0

可以看到如果使用单调栈,上面会有两个连续出栈的过程,这就是相比暴力法改进了很多的地方。

确定了处理流程之后,我们还要考虑实现的一些细节:

  1. 递减的条件是 >= 还是 > ? 是 >=;(试想 [ 1 ,1 ,2 ]对于第一个 1 的答案应该是 2-0=2,所以第二个 1 也要入栈)

  2. 出栈的条件是 < ,什么情况入栈呢?看到上面的例子里在啊 i =5 的时候是有连续出栈的,因此只要满足 < 需要连续出栈,while 循环来封装。

class Solution {
    public int[] dailyTemperatures(int[] T) {
        int size=T.length;
        int[] ans=new int[size];

        Deque<Integer> stack=new ArrayDeque<>();

        int i=0;
        while(i<size){
            int temp=T[i];
            //出栈条件是不满足递减,并且可能一直不满足,是 while 而不是 if
            while(!stack.isEmpty() && T[stack.peek()]<temp){
                int j=stack.pop();//出栈
                ans[j]=i-j;//写答案
            }
            stack.push(i);//如果没有异常就入栈
            i++;
        }
        return ans;
    }
}

三、暴力法改进

直接使用暴力法能否再进一步改进呢?

如果倒过来看这个问题,ans 仍然记为结果数组。

以题目的示例来看这个过程:[73, 74, 75, 71, 69, 72, 76, 73]

  • i = 7,显然ans[ 7 ]=0;
  • i = 6,和后一个比较,显然 ans[ 6 ]=0;
  • i = 5,和后一个比较,ans[ 5 ]=1;
  • i = 4,和后一个比较,ans[ 4 ]=1;
  • i = 3,和后一个比较,71>69,而 ans[ 4 ] !=0说明有比 69 大的,我们只需要看比 69 大的那个数是不是也大于 71 ,找到 T[ 3+1+ans[ 3+1 ] ]=76,大于71,所以我们找到了答案就是,3+1+ans[ 3+1 ] - 3=2.

到这一步的时候我们发现了两个问题:

  1. 这样利用后面结果的“跳跃查找”方式,比暴力法合理了很多;
  2. 如果一次跳跃之后仍然得不到结果,比如 ans[4] !=0,
    但是,对应的 T[ 4+ans[4] ] 也并不 > 当前的 T[i] ,那么如果对应的
    ans[ 5 ]!=0,就不能停止,而要继续跳跃。

这种做法应该是动态规划,状态转移方程就是:

  • 若 T[i] < T[i+1],那么 ans[i]=1;
  • 若 T[i] > T[i+1]
    • ans[i+1]=0,那么 ans[i] = 0;
    • ans[i+1]!=0,那么比较 T[i] 和 T[ i+1 + res[i+1] ] ,这两个作为新的 T[i] 和 T[i+1]

从第二种情况看,“前一个状态”并不能完全解决当前状态的问题,有可能会继续追溯,个人认为这种方法更像是跳跃的递归不是动态规划

但是这道题目,可能对栈的使用和频繁增删操作,时间大于数组的直接访问,用这种递归方法时间远远比单调栈少?(又或者只是力扣日常出bug)
最终代码如下:

class Solution {
    public int[] dailyTemperatures(int[] T) {
        int[] res = new int[T.length];
        res[T.length - 1] = 0;
        for (int i = T.length - 2; i >= 0; i--) {
            for (int j = i + 1; j < T.length; j += res[j]) {
                if (T[i] < T[j]) {
                    res[i] = j - i;
                    break;
                } else if (res[j] == 0) {
                    res[i] = 0;
                    break;
                }
            }
        }
        return res;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值