标题:股票价格跨度
出处:901. 股票价格跨度
6 级
设计一个算法,该算法收集某些股票的每日价格,并返回该股票当日价格的跨度。
今天股票价格的跨度被定义为股票价格小于或等于今天价格的最大连续日数(从今天开始往回数,包括今天)。
实现 StockSpanner \texttt{StockSpanner} StockSpanner 类:
示例 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
题目要求计算股票当日价格的跨度,即从当前日期开始往回数,股票价格小于或等于当日价格的最大连续日数。为了得到股票当日价格的跨度,需要找到历史数据中的股票价格大于当日价格的最大日期。假设当前日期是 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 i−j 天都满足股票价格小于或等于日期 i i i 的价格,日期 i i i 的股票价格跨度即为 i − j i - j i−j。
为了计算股票价格跨度,可以使用单调栈。由于需要比较股票价格和计算跨度,因此单调栈需要同时存储日期和股票价格,满足从栈底到栈顶的股票价格单调递减。单调栈内的元素格式是 [ 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 方法,进行如下操作:
如果栈不为空且栈顶元素的股票价格小于或等于当日股票价格,则将栈顶元素出栈,重复该操作直到栈为空或者栈顶元素的股票价格大于当日股票价格;
计算历史数据中的股票价格大于当日股票价格的最大日期 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=index−prev;
将 [ index , price ] [\textit{index}, \textit{price}] [index,price] 入栈,然后将 index \textit{index} index 加 1 1 1;
返回 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 prev≥0,假设存在日期 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} index−prev。
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。