构建具有以下功能的数据结构:
set(arr,n)
- 使用长度为arr
的数组n
初始化结构。时间O(n)
fetch(i)
- 抓取arr[i]
。时间O(log(n))
invert(k,j)
- (当0 <= k <= j <= n
时)反转子数组[k,j]
。含[4,7,2,8,5,4]
的{{1}}含义变为invert(2,5)
。时间[4,7,4,5,8,2]
如何在二进制搜索树中保存索引并使用表示索引被反转的标志?但如果我做了多于1次反转,那就搞砸了。
答案 0 :(得分:1)
以下是我们如何设计这样的数据结构。 实际上,使用平衡二叉搜索树是一个好主意。
首先,让我们将数组元素存储为成对(index, value)
。
当然,元素按索引排序,因此树的有序遍历将以原始顺序生成数组。
现在,如果我们维护一个平衡的二叉搜索树,并在每个节点中存储子树的大小,我们就可以在O(log n)
中进行获取。
接下来,让我们只假装我们存储索引。
相反,我们仍然按照(index, value)
对的方式排列元素,但只存储值。
index
现在隐式存储 ,可按如下方式计算。
从根目录开始,然后转到目标节点。
每当我们移动到左子树时,index
都不会改变。
移动到右侧子树时,将左子树的大小加上一个(当前顶点的大小)添加到index
。
我们在这一点上得到的是一个存储在平衡二叉搜索树中的固定长度数组。它需要O(log n)
来访问(读或写)任何元素,而不是O(1)
对于普通的固定长度数组,所以现在是时候为所有麻烦获得一些好处。
下一步是根据左侧部分所需的大小设计一种方法,将拆分我们的数组分为O(log n)
中的左右部分,合并串联的两个数组。
此步骤引入了对我们选择的平衡二叉搜索树的依赖性。
Treap是明显的候选者,因为它建立在拆分和合并基元之上,因此这项改进是免费的。
也许也可以在O(log n)
中拆分Red-black tree或Splay tree(虽然我承认我自己并没有试图弄清楚细节)。
现在,结构已经比数组更强大了:它允许在O(log n)
中拆分和连接“数组”,尽管元素访问也和O(log n)
一样慢。
请注意,如果我们此时仍明确存储index
,则无法执行此操作,因为在拆分或合并操作的右侧部分会打破索引
最后,是时候介绍反转操作了。
让我们在每个节点中存储一个标志,以指示是否必须反转该节点的整个子树。
这个标志将是懒惰的传播:每当我们访问节点时,在做任何事情之前,检查标志是否为true
。
如果是这种情况,请交换左右子树, toggle (true <-> false
)两个子树的根节点中的标志,并将当前节点中的标志设置为{{1 }}
现在,当我们想要反转一个子阵列时:
false
)中间(子阵列)部分根目标中的标志,