股票价格波动

标签: 设计 哈希表 数据流 有序集合 堆(优先队列)

难度: Medium

给你一支股票价格的数据流。数据流中每一条记录包含一个 时间戳 和该时间点股票对应的 价格 。

不巧的是,由于股票市场内在的波动性,股票价格记录可能不是按时间顺序到来的。某些情况下,有的记录可能是错的。如果两个有相同时间戳的记录出现在数据流中,前一条记录视为错误记录,后出现的记录 更正 前一条错误的记录。

请你设计一个算法,实现:

  • 更新 股票在某一时间戳的股票价格,如果有之前同一时间戳的价格,这一操作将 更正 之前的错误价格。
  • 找到当前记录里 最新股票价格 。最新股票价格 定义为时间戳最晚的股票价格。
  • 找到当前记录里股票的 最高价格 。
  • 找到当前记录里股票的 最低价格 。

请你实现 StockPrice 类:

  • StockPrice() 初始化对象,当前无股票价格记录。
  • void update(int timestamp, int price) 在时间点 timestamp 更新股票价格为 price 。
  • int current() 返回股票 最新价格 。
  • int maximum() 返回股票 最高价格 。
  • int minimum() 返回股票 最低价格 。

示例 1:

输入:
["StockPrice", "update", "update", "current", "maximum", "update", "maximum", "update", "minimum"]
[[], [1, 10], [2, 5], [], [], [1, 3], [], [4, 2], []]
输出:
[null, null, null, 5, 10, null, 5, null, 2]

解释:
StockPrice stockPrice = new StockPrice();
stockPrice.update(1, 10); // 时间戳为 [1] ,对应的股票价格为 [10] 。
stockPrice.update(2, 5);  // 时间戳为 [1,2] ,对应的股票价格为 [10,5] 。
stockPrice.current();     // 返回 5 ,最新时间戳为 2 ,对应价格为 5 。
stockPrice.maximum();     // 返回 10 ,最高价格的时间戳为 1 ,价格为 10 。
stockPrice.update(1, 3);  // 之前时间戳为 1 的价格错误,价格更新为 3 。
                          // 时间戳为 [1,2] ,对应股票价格为 [3,5] 。
stockPrice.maximum();     // 返回 5 ,更正后最高价格为 5 。
stockPrice.update(4, 2);  // 时间戳为 [1,2,4] ,对应价格为 [3,5,2] 。
stockPrice.minimum();     // 返回 2 ,最低价格时间戳为 4 ,价格为 2 。

提示:

  • 1 <= timestamp, price <= 109
  • updatecurrentmaximum 和 minimum  调用次数不超过 105 。
  • currentmaximum 和 minimum 被调用时,update 操作 至少 已经被调用过 一次 。

Submission

运行时间: 423 ms

内存: 62.7 MB

class StockPrice:
    def __init__(self):
        self.maxPrice = []
        self.minPrice = []
        self.timePriceMap = {}
        self.maxTimestamp = 0

    def update(self, timestamp: int, price: int) -> None:
        heappush(self.maxPrice, (-price, timestamp))
        heappush(self.minPrice, (price, timestamp))
        self.timePriceMap[timestamp] = price
        self.maxTimestamp = max(self.maxTimestamp, timestamp)

    def current(self) -> int:
        return self.timePriceMap[self.maxTimestamp]

    def maximum(self) -> int:
        while True:
            price, timestamp = self.maxPrice[0]
            if -price == self.timePriceMap[timestamp]:
                return -price
            heappop(self.maxPrice)

    def minimum(self) -> int:
        while True:
            price, timestamp = self.minPrice[0]
            if price == self.timePriceMap[timestamp]:
                return price
            heappop(self.minPrice)

Explain

该题解采用堆和哈希表结合的方式来维护股票价格的数据流。堆用于快速检索最大和最小价格,哈希表用于记录每个时间戳对应的价格,并跟踪最新价格。具体来说,使用两个堆,一个最大堆(存储负值以模拟最大堆)用来找到最大价格,一个最小堆用来找到最小价格。更新价格时,将价格与时间戳加入两个堆中,并更新哈希表和最新时间戳。在获取最大和最小价格时,堆顶元素如果与哈希表中的当前价格不符,则将其移除,直到堆顶元素有效。最新价格直接通过最大时间戳在哈希表中查询获得。

时间复杂度: O(log n)(对于update, maximum, 和 minimum操作)

空间复杂度: O(n)


class StockPrice:
    def __init__(self):
        self.maxPrice = []  # 用于存储价格和时间戳的最大堆(存负值)
        self.minPrice = []  # 用于存储价格和时间戳的最小堆
        self.timePriceMap = {}  # 哈希表,键为时间戳,值为价格
        self.maxTimestamp = 0  # 记录最大时间戳

    def update(self, timestamp: int, price: int) -> None:
        heappush(self.maxPrice, (-price, timestamp))  # 更新最大堆
        heappush(self.minPrice, (price, timestamp))  # 更新最小堆
        self.timePriceMap[timestamp] = price  # 更新哈希表
        self.maxTimestamp = max(self.maxTimestamp, timestamp)  # 更新最新时间戳

    def current(self) -> int:
        return self.timePriceMap[self.maxTimestamp]  # 直接返回最新价格

    def maximum(self) -> int:
        while True:
            price, timestamp = self.maxPrice[0]  # 检查最大堆顶元素
            if -price == self.timePriceMap[timestamp]:  # 确认堆顶价格有效
                return -price
            heappop(self.maxPrice)  # 移除无效的堆顶元素

    def minimum(self) -> int:
        while True:
            price, timestamp = self.minPrice[0]  # 检查最小堆顶元素
            if price == self.timePriceMap[timestamp]:  # 确认堆顶价格有效
                return price
            heappop(self.minPrice)  # 移除无效的堆顶元素

Explore

在`maximum`和`minimum`方法中,通过检查堆顶元素的价格与哈希表中对应时间戳的价格是否一致来确保价格的有效性。如果不一致,说明这个价格在之后被更新过,因此这个堆顶元素现在是无效的,将其从堆中移除。这个过程会重复进行,直到找到一个有效的堆顶元素。这样可以确保返回的价格总是有效的。

通过在`maximum`和`minimum`方法中删除不再有效的堆顶元素,处理累积的无效元素。这种处理是必要的,因为每次更新操作可能会导致之前的价格失效。尽管这增加了方法的执行时间,因为每次调用可能涉及多次堆操作(如`heappop`),但通常情况下,这种影响是可接受的,特别是当价格更新不频繁或者价格变动在一定范围内时。不过,极端情况下,如果有大量冗余数据,性能可能会受到影响。

在每次调用`update`方法时,都会更新哈希表`timePriceMap`来记录每个时间戳对应的价格,并更新一个变量`maxTimestamp`保持迄今为止的最大时间戳。在`current`方法中,直接通过`maxTimestamp`从哈希表中获取最新时间戳对应的价格。这样可以保证,无论何时调用`current`方法,都能够返回最新的股票价格。

使用两个堆(一个最大堆和一个最小堆)可以有效地提供对最大和最小股票价格的快速访问,这是因为堆结构能够让我们快速地找到最大值和最小值(堆顶元素)。其他数据结构如平衡二叉搜索树虽然也能提供这些功能,并支持更快的删除和搜索操作,但在简化实现和减少维护复杂性方面,使用堆更为直接和有效。此外,堆的插入和删除操作时间复杂度为O(log n),适用于频繁更新和查询极值的场景。