【问题标题】:max element from list after adding a new element and removing the oldest添加新元素并删除最旧元素后列表中的最大元素
【发布时间】:2013-04-11 23:38:53
【问题描述】:

我有一个具有最大元素的整数列表,我需要跟踪列表中的最大元素:

[3, 1, 2] (3 is the max)

每个时间段,我都会得到一个新的随机元素,将其添加到列表的末尾,并在恒定时间内删除列表的第一个元素。所以,在当前时间段结束时,我的列表会变成这样:

    [3, 1, 2]      (3 is the max)
->  [3, 1, 2, -5]  (don't care about max at this moment)
->  [1, 2, -5]     (now 2 is the max)

我可以保留一个优先级队列,以列表中的值为键,进行 O(log(n)) 插入和删除,但我想知道是否有更有效(可能 [摊销] 恒定时间?)的方法来做它。

【问题讨论】:

  • 如果你能比O(log(n))做得更好,我会感到惊讶
  • 也许尝试在cs.stackexchange.com 上发布这个?即使 O(1) 算法是不可能的,我也会有兴趣证明这种算法是不可能的。也许一个归约论点表明,如果你有这样的方法,你可以在 O(N) 或其他的时间内实现排序。
  • 您可以使用 Java 中的 ArrayList 之类的东西来跟踪每个值的出现次数(这是 Pieter Geerkens 在他的回答中建议的);这有一个丑陋的缺点,虽然 k 是列表元素绝对值的最大值,但它会占用 O(k) 空间。您可以通过使用散列结构来保持计数保持较小,这将花费您 O(n) 空间和发生计数器的摊销 (!) O(1) 时间。
  • 另一方面,队列中任何一个元素的出现次数并没有真正的帮助;当最大元素的最后一次出现被删除时,您仍然需要搜索新的最大值。
  • @torquestomp:就是这样。如果您有一个数据结构,您可以在小于 log(n)(组合时间)内插入和删除项目,那么您可以比 n log n 更快地排序。我们已经知道这样做是不可能的。一些堆变体允许 O(1) 插入,但删除总是 log(n)(有时摊销)。

标签: algorithm


【解决方案1】:

【讨论】:

  • 通过按计划重新计算后缀最大值可以获得最坏情况 O(1)。参见例如my implementation in C++.
  • 不错!双端队列的实现特别漂亮。
  • @DavidEisenstat 您能否在答案中解释您的数据结构(或代码中的 cmets)?我很感兴趣,但 C++ 不是我的母语。
  • @Dan 在高层次上,每个节点都有一个“max”字段,用于存储从该节点到其后面某个节点的最大值。我们不断地从头到尾扫描,更新该字段。由于扫描每次 push/pop 操作只需要一步,因此结构是上一次扫描最后更新的节点,然后是当前扫描最后更新的节点,然后是从未更新的节点。我们维护(i)自上次扫描开始以来到达的节点和(ii)自当前扫描开始以来到达的节点的最大值。不幸的是,魔鬼在细节中,我还没有时间写出来。
  • 我的库带有一个详尽的测试套件,用于短序列操作。
【解决方案2】:

案例:

  1. New >= CurrentMax 并且首先:CurrentMax 更新为 New;
  2. 新的
  3. New = Current Max:搜索以确定新的 CurrentMax;

但是:

为了使第 3 种情况高效,您需要按插入顺序维护一个(双向链接)元素列表,并按大小顺序维护一个树。然后您只需在列表和树中执行插入/删除,并从树中读取新的当前最大值。这与您的树实现一样高效,O(N) 构建插入顺序列表,O(N log N) 构建排名树。

保存列表元素的数据结构如下所示:

public class Element {
  Element PrevInsertionOrder;
  Element NextInsertionOrder;
  Element RankingTreeParent;
  Element RankingTreeLeftChild;
  Element RankingTreeRightChild;
  int Data;
}

更新:

通过维护具有出现计数的不同键值的第二个(双向链接)列表(而不是上面的第二个树),执行 MAX 确定的时间似乎是 O(1)。

【讨论】:

  • 这个恒定的时间如何发展?如何在 O(1) 时间内插入按大小排序的列表?
  • 这仍然不满足未来的 O(1) 要求。在平均情况下,树的插入和删除是 O(log(n))。
  • @torquestomp:这就是为什么我从帖子中删除了该声明,以说明它将受到树实现效率的限制。 (如您所说,很可能是 O(log N)。)
  • 该问题明确说明的目的是确定是否存在可以击败 O(log N) 运行时间进行连续操作的算法,因此这篇文章没有任何价值,除非那里有一个树结构以某种方式保证 O(1) 插入/删除时间。
  • 第二个带有出现次数的链表如何在 O(1) 中解决这个问题?如果没有随机访问,使用链表跟踪出现次数的最佳方法是 O(n),因为您必须迭代才能找到要插入的元素。为了使第二个链表有用,它必须保持排序,这不仅无助于跟踪出现次数的复杂性,而且会花费和额外的 O(n) 才能使插入保持排序。跨度>
猜你喜欢
  • 2020-12-28
  • 1970-01-01
  • 2017-06-27
  • 1970-01-01
  • 1970-01-01
  • 2014-06-08
  • 1970-01-01
  • 1970-01-01
  • 2016-08-01
相关资源
最近更新 更多