有效的数据结构,用于序列的平均值

时间:2013-03-19 21:09:24

标签: algorithm data-structures

我需要设计一个数据结构,它可以有效地支持对存储的(我认为合适的)数字序列的以下操作:

  • 将整数x添加到序列的第一个i元素
  • 在序列的末尾附加一个整数k
  • 删除序列的最后一个元素
  • 检索序列中所有元素的平均值

实施例

以空序列[]

开头
  • 追加0([0]
  • 追加5([0, 5]
  • 追加6([0, 5, 6]
  • 将3添加到序列中的前2个元素([3, 8, 6]
  • 检索平均5.66([3, 8, 6]
  • 删除最后一个元素([3, 8]
  • 检索平均值5.5([3, 8]

以前的工作

我考虑过使用Fenwick TreesTopcoder Editorial),但为此,我需要指定Fenwick树初始化的序列的最大大小,这不一定是知道的。但是如果我有一个序列可以支持的最大元素数量,我可以在O(lg N)上支持这些操作,如果我还保存序列中所有元素的总和。

编辑:问题是Codeforces problem,我需要所有操作的子线性运行时间,因为在最坏的情况下,添加到第一个元素可能是与添加到整个序列相同

6 个答案:

答案 0 :(得分:6)

您是否考虑过使用链表加上当前的长度和总和?对于每个操作,您可以通过不断的额外工作来维持当前平均值(您知道列表的长度和总和,并且所有操作都以常量方式更改这两个值)。

唯一的非常量操作是将常量添加到任意前缀,这需要时间与前缀的大小成比例,因为您需要调整每个数字。

要使所有操作保持不变(摊销),需要更多工作。而不是使用双向链表,用堆栈支持数组。现在,数组中的每个广告位i都包含i处的数字和要添加到i之前的每个元素的常量。 (注意,如果你说“向元素11的每个元素添加3”,则插槽11将包含数字3,但插槽0-10将为空。)现在每个操作都像以前一样,除了附加一个新元素涉及标准的数组加倍技巧,当您从队列末尾弹出最后一个元素时,您需要(a)在该插槽中添加常量,以及(b)从插槽i添加常量值到插槽i-1的常量。所以对你的例子来说:

追加0:[(0,0)], sum 0, length 1

追加5:([(0,0),(5,0)], sum 5, length 2

追加6:[(0,0),(5,0),(6,0)], sum 11, length 3

将3添加到序列中的前2个元素:[(0,0),(5,3),(6,0)], sum 17, length 3

检索平均5.66

删除最后一个元素[(0,0),(5,3)], sum 11, length 2

检索平均值5.5

删除最后一个元素[(0,3)], sum 3, length 1

这里有一些Java代码可能更清楚地说明了这个想法:

class Averager {
  private int sum;
  private ArrayList<Integer> elements = new ArrayList<Integer>();
  private ArrayList<Integer> addedConstants = new ArrayList<Integer>();

  public void addElement(int i) {
    elements.add(i);
    addedConstants.add(0);
    sum += i;
  }

  public void addToPrefix(int k, int upto) {
    addedConstants.set(upto, addedConstants.get(upto) + k);
    sum += k * (upto + 1);
    // Note: assumes prefix exists; in real code handle an error
  }

  public int pop() {
    int lastIndex = addedConstants.length() - 1;

    int constantToAdd = addedConstants.get(lastIndex);
    int valueToReturn = elements.get(lastIndex);
    addedConstants.set(
      lastIndex-1,
      addedConstants.get(lastIndex-1) + constantToAdd);
    sum -= valueToReturn;
    elements.remove(lastIndex);
    addedConstants.remove(lastIndex);
    return valueToReturn + constantToAdd;
    // Again you need to handle errors here as well, particularly where the stack
    // is already empty or has exactly one element
  }

  public double average() {
    return ((double) sum) / elements.length();
  }
}

答案 1 :(得分:2)

听起来像Doubly Linked List,保持头尾参考,以及当前的总和和计数。

  

将整数x添加到序列的前i个元素

从* head开始,添加x,下一项。重复i次。 sum += i*x

  

在序列的末尾附加一个整数k

从* tail开始,使用head = tail,tail = null创建新项目。相应地更新* tail,sum和count。

  

删除序列的最后一个元素

将* tail更新为* tail-&gt; prev。更新总和,减量计数

  

检索平均值5.5([3,8])

退还金额/计数

答案 2 :(得分:1)

这个数据结构可以只是一个元组(N,S),其中N是计数,S是总和和一堆数字。没有什么花哨。所有操作都是O(1),除了第一个是O(i)。

答案 3 :(得分:1)

我建议您尝试使用Binary Indexed Tree

它们允许您以O(Log(n))访问累积频率。

您还可以按顺序log(i)添加到第一个i元素。

然而,不是将第一个i元素增加X,只需将第n个元素增加X.

要删除最后一个元素,可能还有另一棵树,它累计删除了多少。 (因此,不是删除,而是将该数量添加到另一棵树中,在访问第一棵树时,您总是从结果中减去该数量。)

对于追加,我建议你从一张2 * N大小的树开始 这会给你空间。然后,如果您的大小超过2 * N,请添加另一个大小为2 * N的树。 (不完全确定最好的方法,但希望你能搞清楚)。

答案 4 :(得分:1)

为了满足第一个要求,您可以维护添加操作的单独数据结构。基本上,它是范围和增量的有序集合。您还要保留这些添加项的总和。因此,如果您在前三个项目中添加了5个项目,然后在前10个项目中添加了12个项目,那么您将拥有:

{3, 5}
{10, 12}

这些加法的总和是(3*5) + (10*12) = 135。

当要求提供金额时,您需要提供项目总和加上这些项目的总和。

唯一的麻烦就是当您删除列表中的最后一项时。然后你必须经历这些添加的集合,以找到包含最后一个项目(你正在删除的项目)的任何项目。该数据结构可以是哈希映射,其中键是索引。因此,在上面的示例中,您的哈希映射将是:

key: 3  value: 5
key: 10 value: 12

每当您执行第一次操作时,都会检查哈希映射以查看是否已存在具有该键的项目。如果是这样,您只需更新其中的值,而不是添加新的增量。并相应地更新总和。

有趣。你甚至不需要额外增加额外的金额。你可以在你的时候更新总和。

从列表中删除最后一项时,检查具有该键的项目的哈希映射。如果有,则删除该项,减少该键,然后将其添加回哈希映射(或使用该键更新现有项目(如果有)。

所以,使用mattedgod提出的双向链表,并提供他提出的总和。然后使用此哈希映射来维护列表中的添加集合,相应地更新总和。

答案 5 :(得分:0)

第174轮问题制定者已经发布了本轮的社论。你可以找到它here。您还可以查看一些可接受的解决方案:PythonC++