题目描述
设计一个算法收集某些股票的每日报价,并返回该股票当日价格的 跨度 。
当日股票价格的 跨度 被定义为股票价格小于或等于今天价格的最大连续日数(从今天开始往回数,包括今天)。
例如,如果未来 7 天股票的价格是 [100,80,60,70,60,75,85],那么股票跨度将是 [1,1,1,2,1,4,6] 。
实现 StockSpanner 类:
StockSpanner() 初始化类对象。
int next(int price) 给出今天的股价 price ,返回该股票当日价格的 跨度 。
做题情况
- 做出来且思路与标答一致
- 做出来但思路较为复杂
- 有思路,但时间复杂度较高无法通过 ☑
- 没有思路
自己的想法
跨度定义为小于或等于今天价格的最大连续日数,即我们要找到最近大于自己的价格。自己的想法是在进行next函数操作时,记录当前点最近大于自己的价格。当我们在求每个点的最近大于自己的价格时,我们向前遍历,如果上一个点大于当前点,则停止,如果不大于,就直接转到上一个点的最近大于自己的价格,重复判断当前点是否大于那个点,直到找到大于当前点的点为止。
但考虑最差情况,我们在计算当前点时,都有可能遍历到第一个天,即时间复杂度为O(n*n),在本题的规模下有超时的风险。
标答:单调栈
维护一个逐渐递减的栈。如果当前点小于栈顶的元素,则直接入栈;如果当前点大于栈顶的元素,则弹出栈顶的元素并继续比较,直到栈顶的元素小于当前点停止,之后将当前点压入栈中。同时为了统计当前点与最近大于自己的点之间的点数,我们对栈中的所有元素维护一个差值,代表当前点到上一点之间的价格个数。
因此,如果当前点小于栈顶的元素,则差值为1,压入当前点并返回1;如果当前点大于栈顶元素,则进行出栈,并将差值加上出栈元素的差值(相当于将两段合并为一段),最终压入当前点,并返回当前点的差值。
时间复杂度:因为n为调用next函数的次数,每次会引入一个点,每个点最多进行入栈和出栈两次操作,所以时间复杂度为O(n)。
实际代码
class StockSpanner {
public:
stack<pair<int,int>> sta;
StockSpanner()
{
sta.push(make_pair(0,0));
}
int next(int price)
{
if (price<sta.top().first)
{
sta.push(make_pair(price,1));
return 1;
}
else
{
pair<int,int> pai=make_pair(price,1);
while (!sta.empty()&&price>=sta.top().first)
{
pai.second+=sta.top().second;
sta.pop();
}
sta.push(pai);
return pai.second;
}
}
};
总结
其实自己想的思路已经蛮接近了,都是不断跳向上一个较大值。不同的是自己是用较小值来过度的,而标答是维护一个栈,将较小值直接删除了,从当前点直接跳向上一个较大值,因此不需要多次重复调用较小值,在O(n)的时间就可以实现。
另外关于栈的时间复杂度自己也应该学习一下,当引用栈时,每次引入一个点,一个点最多进行两次操作,所以O(n)内就可以实现。用指针或者双指针也是这样的情况,之后遇到后可以留意一下。