【问题标题】:Efficiently counting independent events in a given time range有效计算给定时间范围内的独立事件
【发布时间】:2020-09-27 07:19:13
【问题描述】:

问题描述

我多次遇到这个问题,并且一直想知道我的解决方案是最优的还是(更有可能)有更好的解决方案。

假设我的组件接收由时间和字符串组成的事件。对于我收到的每个事件,我需要返回在最后x 秒内看到了多少个独立字符串。 (x 是可配置的,但在执行开始时固定)。 “最后x 秒”是指在具有最高时间戳的事件发生时结束并持续x 秒(包括两端)的时间范围。

让我举个例子。我以给定的顺序收到以下事件(由 (time, string) 对表示),并且对于每个事件,我都会显示预期的返回值,假设为 x = 5

  • (1, "a") → 1
  • (2, "b") → 2
  • (3, "a") → 2
  • (7, "c") → 3
  • (9, "c") → 1
  • (8, "d") → 2

我们不能假设事件以正确的顺序出现,但我们可以假设差异很小,即,如果您将事件放在有序列表中,则在大多数情况下,您会将事件添加到末尾列表或非常接近它。

此外,这里的字符串是一种简化。它们实际上是可以比较相等性和计算哈希值的对象,但不能排序,也不能做花哨的事情。 (在剩下的问题中,我仍将这些对象称为“字符串”。)


我的解决方案

我会使用两种数据结构:双端队列和哈希映射。前者包含按时间顺序排列的事件,后者包含在最后x 秒内看到的字符串以及它们被看到的时间的计数器。

对于收到的每个事件,我都会将其添加到队列中并增加地图中的计数器。然后我将移动到队列的开头并从中删除所有时间戳太旧(即低于time_of_last_event - x)的事件;对于每个删除的事件,我会减少映射中的相应计数器,如果其计数器为零,则从映射中删除条目。最后地图的大小是我必须返回的数字。

如果乱序事件经常发生,但事件“几乎是有序的”,我可以考虑使用双链表而不是双端队列;插入事件时,我会从其末端向后搜索以找到插入事件的合适位置。这将使我免于进行过多的重新分配,但我不确定为我插入的每个事件分配内存是否会在性能方面有所回报。

假设在队列末尾插入恒定时间,从其开头进行恒定时间移除以及哈希映射中的恒定时间操作,我会说对该算法的每次调用都将摊销恒定时间(在长运行,我将删除尽可能多的条目,因此平均每次调用一个)。


主要问题是:有没有比所描述的算法更好的算法? 我说的更好是指运行速度更快或使用更少内存的算法。

还有几个问题

  • 这个算法有什么问题吗?
  • 这是一个众所周知的问题吗?它有名字吗?我找不到任何东西,但我可能搜索了错误的关键字。

【问题讨论】:

  • 为什么这个例子有 (​​8, "d") → 2?我会想到 3(“a”、“c”和“d”)?
  • 插入 (8, "d") 时,列表中的最新事件是 (9, "c")。计算时间范围,使其在列表中最新事件的时间结束,因此它是 [4, 9]。在此范围内,事件为(7, "c")(8, "d")(9, "c")
  • 啊,确实。错过了最新事件的时间戳为 9

标签: algorithm data-structures


【解决方案1】:

如果您按顺序接收事件,则您的解决方案足够有效,但如果不是,算法将变成二次的,因为将新元素插入到排序位置的双向链表中将需要线性时间(就列表的大小)。如果您将 x 视为常数,这可能不是问题,因为总时间复杂度仍然是 O(n)。但是,如果您将 x 视为一个变量,那么这不是最优的。

解决方案是使用 Min Heap 代替队列或双向链表:

在堆中插入每个新元素,以时间戳为键。当堆的大小比 x 大 1 时,删除堆的根,因为它表示与堆中的其他 x 个条目相比最旧的条目.

对于其余部分,您可以像以前一样继续使用哈希映射。

由于从堆中插入和删除的时间复杂度为O(logx),因此总时间复杂度为O(nlogx)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多