我需要设计一个数据结构,它可以有效地支持对存储的(我认为合适的)数字序列的以下操作:
x
添加到序列的第一个i
元素k
以空序列[]
[0]
)[0, 5]
)[0, 5, 6]
)[3, 8, 6]
)[3, 8, 6]
)[3, 8]
)[3, 8]
)我考虑过使用Fenwick Trees(Topcoder Editorial),但为此,我需要指定Fenwick树初始化的序列的最大大小,这不一定是知道的。但是如果我有一个序列可以支持的最大元素数量,我可以在O(lg N)
上支持这些操作,如果我还保存序列中所有元素的总和。
编辑:问题是Codeforces problem,我需要所有操作的子线性运行时间,因为在最坏的情况下,添加到第一个元素可能是与添加到整个序列相同
答案 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)