单调栈题目:每日温度

本文介绍了如何使用单调栈解决LeetCode上的6级难度问题——每日温度。通过遍历温度数组,利用单调栈避免重复计算,找到每个温度后面第一个更高温度所需的等待天数,实现了O(n)的时间复杂度解决方案。

题目

标题和出处

标题:每日温度

出处:739. 每日温度

难度

6 级

题目描述

要求

给定整数数组 temperatures \texttt{temperatures} temperatures 表示每日温度,返回数组 answer \texttt{answer} answer,其中 answer[i] \texttt{answer[i]} answer[i] 是在第 i \texttt{i} i 天之后出现更高温度需要等待的天数。如果在将来没有更高温度,则 answer[i]   =   0 \texttt{answer[i] = 0} answer[i] = 0

示例

示例 1:

输入: temperatures   =   [73,74,75,71,69,72,76,73] \texttt{temperatures = [73,74,75,71,69,72,76,73]} temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0] \texttt{[1,1,4,2,1,1,0,0]} [1,1,4,2,1,1,0,0]

示例 2:

输入: temperatures   =   [30,40,50,60] \texttt{temperatures = [30,40,50,60]} temperatures = [30,40,50,60]
输出: [1,1,1,0] \texttt{[1,1,1,0]} [1,1,1,0]

示例 3:

输入: temperatures   =   [30,60,90] \texttt{temperatures = [30,60,90]} temperatures = [30,60,90]
输出: [1,1,0] \texttt{[1,1,0]} [1,1,0]

数据范围

  • 1 ≤ temperatures.length ≤ 10 5 \texttt{1} \le \texttt{temperatures.length} \le \texttt{10}^\texttt{5} 1temperatures.length105
  • 30 ≤ temperatures[i] ≤ 100 \texttt{30} \le \texttt{temperatures[i]} \le \texttt{100} 30temperatures[i]100

解法

思路和算法

最简单的做法是对于数组 temperatures \textit{temperatures} temperatures 中的每个元素,向后遍历找到第一个比当前元素大的元素,计算下标差。假设数组 temperatures \textit{temperatures} temperatures 的长度是 n n n,上述做法的时间复杂度是 O ( n 2 ) O(n^2) O(n2),会超出时间限制,因此需要优化。

上述做法中存在重复计算。对于同一个下标 i i i,可能存在 m m m 个下标 j 1 j_1 j1 j m j_m jm,满足对于每个 j k j_k jk 1 ≤ k ≤ m 1 \le k \le m 1km)都有 temperatures [ i ] \textit{temperatures}[i] temperatures[i] temperatures [ j k ] \textit{temperatures}[j_k] temperatures[jk] 后面的第一个比 temperatures [ j k ] \textit{temperatures}[j_k] temperatures[jk] 大的元素,因此对于每个 j k j_k jk 都有 answer [ j k ] = i − j k \textit{answer}[j_k] = i - j_k answer[jk]=ijk。如果按照上述做法,则下标 i i i 被遍历了 m m m 次。

为了避免重复计算,上述做法应改为:从左到右遍历数组 temperatures \textit{temperatures} temperatures,对于每个下标 i i i,寻找在结果数组 answer \textit{answer} answer 中的所有尚未填入值的下标 j j j(显然 j < i j < i j<i),令 answer [ j ] = i − j \textit{answer}[j] = i - j answer[j]=ij。需要确保结果数组 answer \textit{answer} answer 中的每个元素都用 O ( 1 ) O(1) O(1) 的时间填入一次,总时间复杂度是 O ( n ) O(n) O(n)

可以使用单调栈实现,单调栈存储数组 temperatures \textit{temperatures} temperatures 的下标,满足从栈底到栈顶的下标对应的元素单调递减(非严格)。

创建长度为 n n n 的结果数组 answer \textit{answer} answer,初始时对于所有 0 ≤ i < n 0 \le i < n 0i<n 都有 answer [ i ] = 0 \textit{answer}[i] = 0 answer[i]=0。从左到右遍历数组 temperatures \textit{temperatures} temperatures,对于每个下标 i i i,进行如下操作:

  1. 如果栈不为空且栈顶下标对应的温度低于当前温度(即 temperatures [ i ] \textit{temperatures}[i] temperatures[i]),则将栈顶下标出栈,用 j j j 表示出栈下标,则令 answer [ j ] = i − j \textit{answer}[j] = i - j answer[j]=ij,重复该操作直到栈为空或者栈顶下标对应的温度高于或等于当前温度;

  2. i i i 入栈。

遍历结束时,返回 answer \textit{answer} answer

上述解法的正确性说明如下。

  1. 上述操作中,在将下标 i i i 入栈之前会将对应温度低于 temperatures [ i ] \textit{temperatures}[i] temperatures[i] 的下标出栈,只有当栈为空或者栈顶下标对应的温度高于或等于 temperatures [ i ] \textit{temperatures}[i] temperatures[i] 时才会将下标 i i i 入栈,因此新入栈的下标对应的温度一定不会高于栈内已有下标对应的温度,单调栈满足从栈底到栈顶的下标对应的元素单调递减。

  2. 当遍历到下标 i i i 时,如果单调栈不为空,则栈内下标一定小于 i i i。假设下标 j j j 在栈内,满足 j < i j < i j<i temperatures [ j ] < temperatures [ i ] \textit{temperatures}[j] < \textit{temperatures}[i] temperatures[j]<temperatures[i],考虑两种情况,分别为 j j j 是栈顶下标和 j j j 不是栈顶下标。

    • 如果 j j j 是栈顶下标,假设存在下标 k k k 满足 j < k < i j < k < i j<k<i temperatures [ j ] < temperatures [ k ] \textit{temperatures}[j] < \textit{temperatures}[k] temperatures[j]<temperatures[k],则在遍历到下标 k k k 时,下标 j j j 就会出栈,下标 j j j 在栈内说明尚未在下标 j j j 右边遇到比 temperatures [ j ] \textit{temperatures}[j] temperatures[j] 大的元素,因此 temperatures [ i ] \textit{temperatures}[i] temperatures[i] temperatures [ j ] \textit{temperatures}[j] temperatures[j] 右边第一个大于 temperatures [ j ] \textit{temperatures}[j] temperatures[j] 的元素。

    • 如果 j j j 不是栈顶下标,假设下标 j ′ j' j 更接近栈顶,则有 j < j ′ j < j' j<j(根据入栈顺序)和 temperatures [ j ] ≥ temperatures [ j ′ ] \textit{temperatures}[j] \ge \textit{temperatures}[j'] temperatures[j]temperatures[j](根据单调栈的性质),当 temperatures [ j ] < temperatures [ i ] \textit{temperatures}[j] < \textit{temperatures}[i] temperatures[j]<temperatures[i] 时必有 temperatures [ j ′ ] < temperatures [ i ] \textit{temperatures}[j'] < \textit{temperatures}[i] temperatures[j]<temperatures[i],因此比下标 j j j 更接近栈顶的下标都会出栈,当 j j j 变成栈顶下标时,即为上一种情况。

以下是示例 1 的计算过程。

  1. 下标 0 0 0 处的元素是 73 73 73,将 0 0 0 入栈, stack = [ 0 : 73 ] \textit{stack} = [0:73] stack=[0:73],其中左边为栈底,右边为栈顶,栈内存储下标,为了方便阅读加上了下标对应的元素值。

  2. 下标 1 1 1 处的元素是 74 74 74,由于 73 < 74 73 < 74 73<74,因此将 0 0 0 出栈, answer [ 0 ] = 1 − 0 = 1 \textit{answer}[0] = 1 - 0 = 1 answer[0]=10=1,将 1 1 1 入栈, stack = [ 1 : 74 ] \textit{stack} = [1:74] stack=[1:74]

  3. 下标 2 2 2 处的元素是 75 75 75,由于 74 < 75 74 < 75 74<75,因此将 1 1 1 出栈, answer [ 1 ] = 2 − 1 = 1 \textit{answer}[1] = 2 - 1 = 1 answer[1]=21=1,将 2 2 2 入栈, stack = [ 2 : 75 ] \textit{stack} = [2:75] stack=[2:75]

  4. 下标 3 3 3 处的元素是 71 71 71,将 3 3 3 入栈, stack = [ 2 : 75 , 3 : 71 ] \textit{stack} = [2:75, 3:71] stack=[2:75,3:71]

  5. 下标 4 4 4 处的元素是 69 69 69,将 4 4 4 入栈, stack = [ 2 : 75 , 3 : 71 , 4 : 69 ] \textit{stack} = [2:75, 3:71, 4:69] stack=[2:75,3:71,4:69]

  6. 下标 5 5 5 处的元素是 72 72 72,由于 69 < 72 69 < 72 69<72 71 < 72 71 < 72 71<72,因此将 4 4 4 3 3 3 出栈, answer [ 4 ] = 5 − 4 = 1 \textit{answer}[4] = 5 - 4 = 1 answer[4]=54=1 answer [ 3 ] = 5 − 3 = 2 \textit{answer}[3] = 5 - 3 = 2 answer[3]=53=2,将 5 5 5 入栈, stack = [ 2 : 75 , 5 : 72 ] \textit{stack} = [2:75, 5:72] stack=[2:75,5:72]

  7. 下标 6 6 6 处的元素是 76 76 76,由于 72 < 76 72 < 76 72<76 75 < 76 75 < 76 75<76,因此将 5 5 5 2 2 2 出栈, answer [ 5 ] = 6 − 5 = 1 \textit{answer}[5] = 6 - 5 = 1 answer[5]=65=1 answer [ 2 ] = 6 − 2 = 4 \textit{answer}[2] = 6 - 2 = 4 answer[2]=62=4,将 6 6 6 入栈, stack = [ 6 : 76 ] \textit{stack} = [6:76] stack=[6:76]

  8. 下标 7 7 7 处的元素是 73 73 73,将 7 7 7 入栈, stack = [ 6 : 76 , 7 : 73 ] \textit{stack} = [6:76, 7:73] stack=[6:76,7:73]

结果数组 answer = [ 1 , 1 , 4 , 2 , 1 , 1 , 0 , 0 ] \textit{answer} = [1, 1, 4, 2, 1, 1, 0, 0] answer=[1,1,4,2,1,1,0,0]

代码

class Solution {
    public int[] dailyTemperatures(int[] temperatures) {
        int length = temperatures.length;
        int[] answer = new int[length];
        Deque<Integer> stack = new ArrayDeque<Integer>();
        for (int i = 0; i < length; i++) {
            int temperature = temperatures[i];
            while (!stack.isEmpty() && temperatures[stack.peek()] < temperature) {
                int j = stack.pop();
                answer[j] = i - j;
            }
            stack.push(i);
        }
        return answer;
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 temperatures \textit{temperatures} temperatures 的长度。需要从左到右遍历数组 temperatures \textit{temperatures} temperatures 计算结果数组 answer \textit{answer} answer 的值,由于每个下标最多入栈和出栈各一次,结果数组 answer \textit{answer} answer 中的每个元素的计算时间是 O ( 1 ) O(1) O(1),因此时间复杂度是 O ( n ) O(n) O(n)

  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 temperatures \textit{temperatures} temperatures 的长度。空间复杂度主要取决于栈空间,栈内元素个数不会超过 n n n

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

伟大的车尔尼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值