Algorithm 查找给定范围内大于x的元素数
给定一个包含n个元素的数组,如何在给定的索引i到索引j的范围内找到大于或等于给定值(x)的元素数(O(logn)复杂度 查询的形式为(i,j,x),这意味着从数组中的第i个元素到第j个元素查找大于x的元素数 数组未排序。i、 对于不同的查询,j&x是不同的。数组的元素是静态的。Algorithm 查找给定范围内大于x的元素数,algorithm,Algorithm,给定一个包含n个元素的数组,如何在给定的索引i到索引j的范围内找到大于或等于给定值(x)的元素数(O(logn)复杂度 查询的形式为(i,j,x),这意味着从数组中的第i个元素到第j个元素查找大于x的元素数 数组未排序。i、 对于不同的查询,j&x是不同的。数组的元素是静态的。 编辑:对于不同的查询,i、j、x都可以不同 只有在对数组进行排序的情况下才有可能。在这种情况下,二进制搜索通过条件的最小值,并通过将索引范围除以其找到的位置,再细分为两个间隔来计算计数。然后计算通过条件的时间间隔的长度
编辑:对于不同的查询,i、j、x都可以不同 只有在对数组进行排序的情况下才有可能。在这种情况下,二进制搜索通过条件的最小值,并通过将索引范围除以其找到的位置,再细分为两个间隔来计算计数。然后计算通过条件的时间间隔的长度 如果数组未排序,并且需要保留其顺序,则可以使用索引排序。综合起来:
作为您使用的索引范围,x
作为您的值
因此,创建大小为m=i1-i0+1的数组并对其进行索引排序。这个任务是O(m.log(m))
其中m=1;
* ^
a[ix[i]={1,2,3,4,5,6,6,9}023<4->i+=j;j> >=1;
* ^
a[ix[i]={1,2,3,4,5,6,6,9}2 1 3 4>=4 j>=1;
* ^
a[ix[i]={1,2,3,4,5,6,6,9}20->停止
*
a[ix[i]]a[ix[i+1]>=x->i=2+1=3 in O(log(m))
所以你需要索引i
和二进制位掩码j
(2的幂)。在第一次设置时,i
为零,j
的最大功率为2,比n
小(或者在本例中为m
)。例如:
i=0; for (j=1;j<=m;j<<=1;); j>>=1;
[1 2 3 4 5 6 8 9] Node 1
[4 5 6 9] [1 2 3 8] Node 2-3
[4 9] [5 6] [1 3] [2 8] Node 4-7
[9] [4] [5] [6] [1] [3] [2] [8] Node 8-15
i=0;对于(j=1;j>=1
和ifj==0
停止else再次进行迭代。最后,您发现值是a[ix[i]
,索引是log2(m)
迭代中的i
,这也是表示m-1
所需的位数
在上面的例子中,我使用condition
a[ix[I]]如果我们事先知道所有查询,那么我们可以通过使用
首先,我们需要根据数组和查询中的所有元素的值对它们进行排序
因此,假设我们有数组[5,4,2,1,3]和查询(0,1,6)和(2,5,2),排序后我们将得到以下结果:[1,2,2,3,4,5,6]
现在,我们需要按降序处理每个元素:
- 如果我们遇到一个来自数组的元素,我们将更新它在Fenwick树中的索引,该索引取O(logn)
- 如果我们遇到一个查询,我们需要检查,在这个查询范围内,树中添加了多少元素,这些元素取O(logn)
对于上述示例,流程将为:
1st element is a query for value 6, as Fenwick tree is empty -> result is 0
2nd is element 5 -> add index 0 into Fenwick tree
3rd element is 4 -> add index 1 into tree.
4th element is 3 -> add index 4 into tree.
5th element is 2 -> add index 2 into tree.
6th element is query for range (2, 5), we query the tree and get answer 2.
7th element is 1 -> add index 3 into tree.
Finish.
因此,总的来说,我们的解决方案的时间复杂度是O((m+n)log(m+n)),其中m和n分别是来自输入数组的查询数和元素数。这是二维正交范围计数查询的特殊变体。
每个元素el[i]
被转换为平面上的点(i,el[i])
查询(i,j,x)
可以转换为计算矩形中的所有点[i,j]x[x,+infty]
对于这种类型的查询,可以使用2D范围树(例如:)
简单的想法是有一棵树,在叶子上存储点
(每个叶包含一个点)按X轴排序。
树的每个内部节点都包含存储子树中所有点的附加树(按Y轴排序)。
使用的空间是O(n logn)
简单版本可以在O(log^2n)
时间内进行计数,但使用
分数级联
这可以减少到O(logn)
Chazelle在1988年提出了更好的解决方案()
到O(n)
预处理和O(logn)
查询时间
您可以找到一些查询时间更好的解决方案,但它们要复杂得多。我会尝试给您一个简单的方法
你一定学过合并排序。
在合并排序中,我们继续将数组划分为子数组,然后重新构建它,但我们不存储已排序的子数组。在这种方法中,我们将它们存储为二叉树的节点
这需要占用nlogn空间和nlogn时间来构建;
现在,对于每个查询,您只需找到子数组,这将在logn中平均完成,在最坏的情况下在logn^2中完成
这些树也被称为芬威克树。
如果你想要一个简单的代码,我可以提供给你。前面的答案描述了一个使用Fenwick树的离线解决方案,但是这个问题可以在线解决(甚至在对阵列进行更新时),复杂度稍差一些。我将使用段树和AVL树描述这样的解决方案(任何自平衡BST都可以做到这一点)
首先,让我们看看如何使用段树解决此问题。我们将通过按覆盖范围保留每个节点中数组的实际元素来完成此操作。因此,对于数组A=[9,4,5,6,1,3,2,8]
,我们将有:
[9 4 5 6 1 3 2 8] Node 1
[9 4 5 6] [1 3 2 8] Node 2-3
[9 4] [5 6] [1 3] [2 8] Node 4-7
[9] [4] [5] [6] [1] [3] [2] [8] Node 8-15
由于我们的段树的高度是log(n)
,并且在每个级别上我们保留了n个元素,所以使用的内存总量是nlog(n)
下一步是对这些数组进行排序,如下所示:
i=0; for (j=1;j<=m;j<<=1;); j>>=1;
[1 2 3 4 5 6 8 9] Node 1
[4 5 6 9] [1 2 3 8] Node 2-3
[4 9] [5 6] [1 3] [2 8] Node 4-7
[9] [4] [5] [6] [1] [3] [2] [8] Node 8-15
注意:首先需要构建树,然后对其排序,以保持原始数组中元素的顺序
现在,我们可以开始范围查询,其工作方式基本上与常规段树中的相同,除非我们找到完全重叠的区间,然后再检查大于X的元素数。这可以通过日志(n)中的二进制搜索完成通过查找大于X的第一个元素的索引,并将其从