单调栈题目:股票价格跨度

题目

标题和出处

标题:股票价格跨度

出处:901. 股票价格跨度

难度

6 级

题目描述

要求

设计一个算法,该算法收集某些股票的每日价格,并返回该股票当日价格的跨度。

今天股票价格的跨度被定义为股票价格小于或等于今天价格的最大连续日数(从今天开始往回数,包括今天)。

  • 例如,如果未来 7 \texttt{7} 7 天股票的价格是 [100,   80,   60,   70,   60,   75,   85] \texttt{[100, 80, 60, 70, 60, 75, 85]} [100, 80, 60, 70, 60, 75, 85],那么股票跨度将是 [1,   1,   1,   2,   1,   4,   6] \texttt{[1, 1, 1, 2, 1, 4, 6]} [1, 1, 1, 2, 1, 4, 6]

实现 StockSpanner \texttt{StockSpanner} StockSpanner 类:

  • StockSpanner() \texttt{StockSpanner()} StockSpanner() 初始化类的对象。
  • int   next(int   price) \texttt{int next(int price)} int next(int price) 给定今天的股票价格是 price \texttt{price} price,返回今天股票价格的跨度。

示例

示例 1:

输入:
["StockSpanner",   "next",   "next",   "next",   "next",   "next",   "next",   "next"] \texttt{["StockSpanner", "next", "next", "next", "next", "next", "next", "next"]} ["StockSpanner", "next", "next", "next", "next", "next", "next", "next"]
[[],   [100],   [80],   [60],   [70],   [60],   [75],   [85]] \texttt{[[], [100], [80], [60], [70], [60], [75], [85]]} [[], [100], [80], [60], [70], [60], [75], [85]]
输出:
[null,   1,   1,   1,   2,   1,   4,   6] \texttt{[null, 1, 1, 1, 2, 1, 4, 6]} [null, 1, 1, 1, 2, 1, 4, 6]
解释:
StockSpanner   stockSpanner   =   new   StockSpanner(); \texttt{StockSpanner stockSpanner = new StockSpanner();} StockSpanner stockSpanner = new StockSpanner();
stockSpanner.next(100); \texttt{stockSpanner.next(100);} stockSpanner.next(100); // 返回 1 \texttt{1} 1
stockSpanner.next(80); \texttt{stockSpanner.next(80);} stockSpanner.next(80); // 返回 1 \texttt{1} 1
stockSpanner.next(60); \texttt{stockSpanner.next(60);} stockSpanner.next(60); // 返回 1 \texttt{1} 1
stockSpanner.next(70); \texttt{stockSpanner.next(70);} stockSpanner.next(70); // 返回 2 \texttt{2} 2
stockSpanner.next(60); \texttt{stockSpanner.next(60);} stockSpanner.next(60); // 返回 1 \texttt{1} 1
stockSpanner.next(75); \texttt{stockSpanner.next(75);} stockSpanner.next(75); // 返回 4 \texttt{4} 4,因为截至今天的最后 4 \texttt{4} 4 个价格(包括今天的价格 75 \texttt{75} 75)小于或等于今天的价格
stockSpanner.next(85); \texttt{stockSpanner.next(85);} stockSpanner.next(85); // 返回 6 \texttt{6} 6

数据范围

  • 1 ≤ price ≤ 10 5 \texttt{1} \le \texttt{price} \le \texttt{10}^\texttt{5} 1price105
  • 至多调用 next \texttt{next} next 方法 10 4 \texttt{10}^\texttt{4} 104

解法

思路和算法

题目要求计算股票当日价格的跨度,即从当前日期开始往回数,股票价格小于或等于当日价格的最大连续日数。为了得到股票当日价格的跨度,需要找到历史数据中的股票价格大于当日价格的最大日期。假设当前日期是 i i i,历史数据中的股票价格大于日期 i i i 价格的最大日期是 j j j,其中 j < i j < i j<i,则从日期 j + 1 j + 1 j+1 到日期 i i i 的连续 i − j i - j ij 天都满足股票价格小于或等于日期 i i i 的价格,日期 i i i 的股票价格跨度即为 i − j i - j ij

为了计算股票价格跨度,可以使用单调栈。由于需要比较股票价格和计算跨度,因此单调栈需要同时存储日期和股票价格,满足从栈底到栈顶的股票价格单调递减。单调栈内的元素格式是 [ index , price ] [\textit{index}, \textit{price}] [index,price],其中 index \textit{index} index 表示日期, price \textit{price} price 表示该日期的股票价格, index \textit{index} index 的初始值是 0 0 0

对于 next \textit{next} next 方法,进行如下操作:

  1. 如果栈不为空且栈顶元素的股票价格小于或等于当日股票价格,则将栈顶元素出栈,重复该操作直到栈为空或者栈顶元素的股票价格大于当日股票价格;

  2. 计算历史数据中的股票价格大于当日股票价格的最大日期 prev \textit{prev} prev,如果栈为空则 prev \textit{prev} prev − 1 -1 1,如果栈不为空则 prev \textit{prev} prev 为栈顶元素的日期,当日股票价格跨度是 span = index − prev \textit{span} = \textit{index} - \textit{prev} span=indexprev

  3. [ index , price ] [\textit{index}, \textit{price}] [index,price] 入栈,然后将 index \textit{index} index 1 1 1

  4. 返回 span \textit{span} span

证明

由于单调栈满足从栈底到栈顶的股票价格单调递减,因此当 [ index , price ] [\textit{index}, \textit{price}] [index,price] 入栈时,单调栈内的所有元素的股票价格都大于 price \textit{price} price。考虑 price \textit{price} price 入栈之前的栈顶日期 prev \textit{prev} prev(如果栈为空则 prev = − 1 \textit{prev} = -1 prev=1), prev \textit{prev} prev 一定是历史数据中股票价格大于 price \textit{price} price 的最大日期。理由如下:

  • 如果 prev = − 1 \textit{prev} = -1 prev=1,则历史数据中的股票价格一定都小于或等于 price \textit{price} price,否则大于 price \textit{price} price 的股票价格一定会在单调栈内;

  • 如果 prev ≥ 0 \textit{prev} \ge 0 prev0,假设存在日期 mid \textit{mid} mid 满足 prev < mid < index \textit{prev} < \textit{mid} < \textit{index} prev<mid<index 且日期 mid \textit{mid} mid 的股票价格大于当日股票价格 price \textit{price} price,则无论日期 mid \textit{mid} mid 的股票价格是否小于日期 prev \textit{prev} prev 的股票价格,日期 mid \textit{mid} mid 一定会入栈,此时栈顶日期是 mid \textit{mid} mid 或者大于 mid \textit{mid} mid 的某个日期,出现矛盾,因此 prev \textit{prev} prev 一定是历史数据中股票价格大于 price \textit{price} price 的最大日期。

由于栈顶日期 prev \textit{prev} prev 是历史数据中股票价格大于 price \textit{price} price 的最大日期,因此从日期 prev + 1 \textit{prev} + 1 prev+1 index \textit{index} index 的股票价格都小于或等于当日股票价格,当日股票价格的跨度即为 index − prev \textit{index} - \textit{prev} indexprev

代码

class StockSpanner {
    Deque<int[]> stack;
    int index;

    public StockSpanner() {
        stack = new ArrayDeque<int[]>();
        index = 0;
    }
    
    public int next(int price) {
        while (!stack.isEmpty() && stack.peek()[1] <= price) {
            stack.pop();
        }
        int prev = stack.isEmpty() ? -1 : stack.peek()[0];
        int span = index - prev;
        stack.push(new int[]{index, price});
        index++;
        return span;
    }
}

复杂度分析

  • 时间复杂度:构造方法的时间复杂度是 O ( 1 ) O(1) O(1),方法 next \textit{next} next均摊时间复杂度是 O ( 1 ) O(1) O(1)。假设总天数是 n n n,则共有 n n n 个日期和股票价格,每个元素(包含日期和对应的股票价格)最多入栈和出栈各一次,因此均摊时间复杂度是 O ( 1 ) O(1) O(1)

  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是总天数。空间复杂度主要取决于栈空间,栈内元素个数不会超过 n n n

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

伟大的车尔尼

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

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

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

打赏作者

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

抵扣说明:

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

余额充值