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


    题目

    标题和出处

    标题:股票价格跨度

    出处: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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    复杂度分析

    • 时间复杂度:构造方法的时间复杂度是 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

  • 相关阅读:
    linux如何下载安装sqoop
    SpringMVC执行流程
    合众汽车选用风河Wind River Linux系统
    kafka分区分配策略
    前端一面高频react面试题(持续更新中)
    在Express框架使用ORM模型访问关系型数据库
    ExecutorService、Callable、Future实现有返回结果的多线程原理解析
    2022华为软挑赛题讲解(CodeCraft-2022)
    rhcsa-8
    初识form表单
  • 原文地址:https://blog.csdn.net/stormsunshine/article/details/121149529