算法--股票价格跨度

97 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第13天,点击查看活动详情

题目

leetcode 股票价格跨度 难度:中等

编写一个 StockSpanner 类,它收集某些股票的每日报价,并返回该股票当日价格的跨度。

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

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

示例:

输入:["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) 小于或等于今天的价格。

提示:

调用 StockSpanner.next(int price) 时,将有 1 <= price <= 10^5。

每个测试用例最多可以调用  10000 次 StockSpanner.next。

在所有测试用例中,最多调用 150000 次 StockSpanner.next。

此问题的总时间限制减少了 50%。

题解

求出小于或等于今天价格的最大连续日数等价于求出最近的一个大于今日价格的日子。如果第 i 天的价格为 A[i],第 j 天的价格为 A[j],满足 i < j 且 A[i] <= A[j],那么在第 j 天之后,第 i 天不会是任何一天询问的答案,因为如果对于第 k, k > j 天而言,第 i 天是最近的一个大于今日价格的日子,但第 j 天出现在第 i 天之后且价格不低于第 i 天,因此出现了矛盾。

有了这样一个结论,我们只需要维护一个单调递减的序列,称之为”单调栈“。例如股票每天的价格为 [11, 3, 9, 5, 6, 4],那么每天结束之后,对应的单调栈分别为:

[11]
[11, 3]
[11, 9]
[11, 9, 5]
[11, 9, 6]
[11, 9, 6, 4]

当我们得到了新的一天的价格(例如 7)时,我们将栈中所有小于等于 7 的元素全部取出,因为根据之前的结论,这些元素不会成为后续询问的答案。当栈顶的元素大于 7 时,我们就得到最近的一个大于 7 的价格为 9。

var StockSpanner = function() {
    this.stack = [];
    this.stack.push([-1, Number.MAX_VALUE]);
    this.idx = -1;
};

StockSpanner.prototype.next = function(price) {
    this.idx++;
    while (price >= this.stack[this.stack.length - 1][1]) {
        this.stack.pop();
    }
    let ret = this.idx - this.stack[this.stack.length - 1][0];
    this.stack.push([this.idx, price]);
    return ret;
};

代码详解

这是一道在线问题,在调用 next 往数据流存入元素的同时,返回连续段不大于当前元素的数的个数。

一个朴素的想法是:使用数组 nums 将所有 price 进行存储,每次返回时往前找到第一个不满足要求的位置,并返回连续段的长度。

实际上我们可以利用「分块」思路对其进行优化,将与连续段的比较转换为与最值的比较。

具体的,我们仍然使用 nums 对所有的 price 进行存储,同时使用 region 数组来存储每个连续段的最大值,其中 region[loc] = x 含义为块编号为 loc 的最大值为 x,其中块编号 loc 块对应了数据编号 idx 的范围 [(loc−1)×len+1,loc×len]。

对于 next 操作而言,除了直接更新数据数组 nums[++idx] = price 以外,我们还需要更新 idx 所在块的最值 region[loc],然后从当前块 loc 开始往前扫描其余块,使用 left 和 right 代指当前处理到的块的左右端点,若当前块满足 region[loc] <= price,说明块内所有元素均满足要求,直接将当前块 loc 所包含元素个数累加到答案中,直到遇到不满足的块或达到块数组边界,若存在遇到不满足要求的块,再使用 right 和 left 统计块内满足要求 nums[i] <= price 的个数。