持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情
今日的LeetCode每日一题是901. 股票价格跨度 - 力扣(LeetCode)。其实一开始以为是一道模拟问题,但是采用模拟会因时间复杂度过高而无法通过,因此采用单调栈的方法。
题目内容
编写一个 StockSpanner 类,它收集某些股票的每日报价,并返回该股票当日价格的跨度。
- 今天股票价格的跨度被定义为股票价格小于或等于今天价格的最大连续日数(从今天开始往回数,包括今天)。
- 例如,如果未来
7
天股票的价格是[100, 80, 60, 70, 60, 75, 85]
,那么股票跨度将是[1, 1, 1, 2, 1, 4, 6]
。
问:请编写一个
StockSpanner
类来计算出最大连续日数
样例
输入:["StockSpanner","next","next","next","next","next","next","next"],
[[],[100],[80],[60],[70],[60],[75],[85]]
输出:[null,1,1,1,2,1,4,6]
解释:
首先,初始化 S = StockSpanner(),然后:
S.next(100) 被调用并返回 1,
S.next(80) 被调用并返回 1,
S.next(60) 被调用并返回 1,
S.next(70) 被调用并返回 2,
S.next(60) 被调用并返回 1,
S.next(75) 被调用并返回 4,
S.next(85) 被调用并返回 6。
注意 (例如) S.next(75) 返回 4,因为截至今天的最后 4 个价格
(包括今天的价格 75) 小于或等于今天的价格。
方法
在读完题目之后,可以有一个直观的想法,就是模拟上述的过程,即:
- 情形1:如果当前价格
price
大于 之前的price
,则将今天的天数加1
- 情形2:如果当前价格
price
小于 于之前的price
, 则从当前位置向前寻找 第一个 大于当前price
的位置
但是这样的过程存在一个问题,在每次满足 情形2 的时候都需要向前去寻找符合条件的元素,这样就会导致时间复杂度过大,相当于是 ,其中 是整个序列的长度。导致时间复杂度变大的主要原因是在向前寻找第一个 大于当前price
的位置的过程。
因为题目中需要去寻找的 股票价格小于或等于今天价格的最大连续日数,这说明理论上符合条件的股票价格位置都是呈现 单调递增 的,因此可以采用 单调栈 的方法解决这道题,来降低查找过程所带来的额外开销。
单调栈:一种满足单调性质的栈结构,通常用于寻找比当前位置大的下一个元素的位置
在栈结构中,我们需要记录两个元素,分别是:
- 股票价格的下标(对应的天数)
idx
- 当天的股票价格
price
因为此时栈中的元素是利用单调栈进行存储,在单调栈中,会保持从栈顶
找栈底
是以一种升序的方式对数据进行存储,所以在调用 next
时方法计算连续天数时,会将当前的价格与栈顶元素进行比较。
- 如果当前价格
price
价格大于或者等于当前栈顶stack.peek()
元素对应的价格,则需要继续找栈中更大的price
位置。 - 如果当前价格
price
价格小于当前栈顶stack.peek()
元素对应的价格,则已经找栈中更大的price
位置。
而整个减少查询的最大过程,就是如何维护一个单调栈。在本题中,在不断的调用 next()
方法中需要将price
进行记录来维持单调栈,因此要维持单调栈,就需要在将不满足单调情况的消灭掉。
即保证插入的 新
price
一定可以满足栈的单调
这就需要在比较栈顶元素时,如果栈顶元素stack.peek()
大于或者等于 price
,则将栈顶元素弹出,来保证上述条件成立,总结下来代码如下:
class StockSpanner {
int idx;
LinkedList<int[]> stk;
public StockSpanner() {
stk = new LinkedList<>();
stk.push(new int[]{-1, Integer.MAX_VALUE});
idx = -1;
}
public int next(int price) {
idx++;
// 寻找大于当前价格的位置
while (price >= stk.peek()[1]){
// 利用pop将小于当前价格为元素清除,而保持栈内的数据是以单调递增存储
stk.pop();
}
int result = idx - stk.peek()[0];
stk.push(new int[]{idx, price});
return result;
}
}