注意:以下代码恰好是C#,但实际上任何语言的答案对我都有帮助。
假设不是实际的集合(例如,List<T>
),我有一系列操作,每个都看起来像这样:
struct ListOperation<T>
{
public enum OperationType { Insert, Remove }
public OperationType Type;
public T Element; // irrelevant for OperationType.Remove
public int Index;
}
有没有办法根据一系列此类操作有效“重建”一个集合?
特别是,我希望避免明显(低效)的实现,基本上只是创建一个List<T>
并调用Insert
和RemoveAt
- 两个O(N)操作 - 为每个元素。
更新:假设操作的“序列”实际上是一个具体的集合,其计数是已知的并且可以通过索引随机访问(例如,像ListOperation<T>[]
一样)。让我们也说已知结果集合的实际数量(但实际上,无论如何,通过计算插入和删除来计算O(N)都是微不足道的)。还有其他想法吗?
答案 0 :(得分:6)
我认为你可以通过使用索引平衡二叉树(二进制树,其中每个节点存储其左右节点的数量)在O(n lg n)中执行此操作。使用这种结构,您可以通过遍历树来查找新元素所属的位置,从而在任何点获得最坏情况的O(lg n)插入或删除,然后执行任何必要的修复以维持平衡条件(例如如果它是一棵红黑树,你会做一个红黑树修复。)
鉴于此设置,您可以在O(n lg n)中将所有操作重放到这样的树结构中,因为每个单独的操作最多需要O(lg n)才能完成。一旦你有了树,你就可以对元素进行顺序遍历以使它们按正确的顺序返回,并且可以在O(n)时间内将所有值附加到结果列表中,以获得O(n lg) n)中。
我会更多地考虑这一点,看看我能否想出一个在线性时间内做到这一点的方法。与此同时,这至少表明在次级时间内可以做到这一点。
答案 1 :(得分:1)
我预感到可能存在O(n)算法。
索引上的Radix-sort 数字。花费O(n)时间。如果从LSB方面完成,这是一个稳定的排序。
假设存在索引 i 的操作,但没有使用较小索引的操作尚未完成。然后,我们可以按正确的顺序重放索引 i 的操作。特别是操作'insert'和'remove'正在做什么对我来说并不清楚。最坏的情况是O(n lg n)与二叉树的想法,但也许重播可以在O(n)中完成,因为它是本地的。
将第2步提升为归纳论证作为正确性的证明。在索引 i 的步骤之后,有一个不变的维护和一个较短的操作列表,所以通过归纳,...(详情)......