你考虑过使用链表加上当前长度和总和吗?对于每个操作,你可以通过额外的常数工作来维护当前平均值(你知道列表的长度和总和,并且所有操作以恒定的方式改变这两个值)。
唯一的非常数操作是将一个常数添加到任意前缀中,这将需要与前缀大小成比例的时间,因为你需要调整每个数字。
要使所有操作都是常数(平摊),需要更多的工作。不要使用双向链表,而是用堆栈支持数组。现在,数组中的每个插槽 i 包含 i 处的数字和要添加到每个元素直至 i 的常数。(请注意,如果你说“将 3 添加到第 11 个元素之前的每个元素”,插槽 11 将包含数字 3,但插槽 0-10 将为空。)现在,每个操作都与之前相同,只是附加新元素涉及标准数组加倍技巧,并且当你从队列末尾弹出最后一个元素时,你需要 (a) 在该插槽中添加常数,以及 (b) 将插槽 i 的常数值添加到插槽 i-1 的常数值中。所以对于你的例子:
添加 0:[(0,0)], 总和 0,长度 1
添加 5:[(0,0),(5,0)], 总和 5,长度 2
添加 6:[(0,0),(5,0),(6,0)], 总和 11,长度 3
将序列中前两个元素加 3:[(0,0),(5,3),(6,0)], 总和 17,长度 3
检索平均值 5.66
删除最后一个元素 [(0,0),(5,3)], 总和 11,长度 2
检索平均值 5.5
删除最后一个元素 [(0,3)], 总和 3,长度 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);
}
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;
}
public double average() {
return ((double) sum) / elements.length();
}
}