以下是问题,一个未排序的数组a[n]
,我需要在kth
范围内找到[i, j]
最小数字,绝对1<=i<=j<=n, k<=j-i+1
。
通常我会使用quick-find
来完成这项工作,但如果有很多不同范围[i, j]
的查询请求,那么速度不够快,我很难找到一个算法来进行查询{ {1}}时间(允许预处理)。
任何想法都表示赞赏。
PS
让我更容易理解问题。允许任何类型的预处理,但查询需要在O(logn)时间内完成。并且会有很多(超过1个)查询,例如找到O(logn)
。
通过范围[i,j] 我的意思是在原始数组中,没有排序或其他东西。
例如,1st in range [3,7], or 3rd in range [10,17], or 11th in range [33, 52]
,查询a[5] = {3,1,7,5,9}
为1st in range [3,4]
,5
为2nd in range [1,3]
,5
为3rd in range [0,2]
。< / p>
答案 0 :(得分:7)
如果允许预处理而不计入时间复杂度,只需使用它来构建子列表,以便您可以有效地找到您正在寻找的元素。与大多数优化一样,这会延长时间。
您的预处理步骤是获取n
个数字的原始列表,并创建一些新的子列表。
这些子列表中的每一个都是原始的一部分,从n
元素开始,扩展为m
元素,然后排序。所以你的原始列表:
{3, 1, 7, 5, 9}
给你:
list[0][0] = {3}
list[0][1] = {1, 3}
list[0][2] = {1, 3, 7}
list[0][3] = {1, 3, 5, 7}
list[0][4] = {1, 3, 5, 7, 9}
list[1][0] = {1}
list[1][1] = {1, 7}
list[1][2] = {1, 5, 7}
list[1][3] = {1, 5, 7, 9}
list[2][0] = {7}
list[2][1] = {5, 7}
list[2][2] = {5, 7, 9}
list[3][0] = {5}
list[3][1] = {5,9}
list[4][0] = {9}
这不是一个便宜的操作(在或空间中),所以你可能想在列表上维护一个“脏”标志,这样你只能在修改后第一次执行它操作(插入,删除,更改)。
事实上,即使更多效率,您也可以使用延迟评估。当您启动时以及执行修改操作时,基本上将所有子列表设置为空列表。然后,每当您尝试访问子列表并且它为空时,在尝试从中获取k
值之前,计算该子列表(仅限该列表)。
确保在需要时仅评估子列表并缓存以防止不必要的重新计算。例如,如果您从未要求3到6子列表中的值,则永远不会计算它。
用于创建所有子列表的伪代码基本上是(for
包含两端的循环):
for n = 0 to a.lastindex:
create array list[n]
for m = 0 to a.lastindex - n
create array list[n][m]
for i = 0 to m:
list[n][m][i] = a[n+i]
sort list[n][m]
懒惰评估的代码有点复杂(但只是一点点),所以我不会为此提供伪代码。
然后,为了找到k
到i
范围内的j
最小数字(其中i
和j
是原始索引) ,你只需查看lists[i][j-i][k-1]
,这是一个非常快速的O(1)操作:
+--------------------------+
| |
| v
1st in range [3,4] (values 5,9), list[3][4-3=1][1-1-0] = 5
2nd in range [1,3] (values 1,7,5), list[1][3-1=2][2-1=1] = 5
3rd in range [0,2] (values 3,1,7), list[0][2-0=2][3-1=2] = 7
| | ^ ^ ^
| | | | |
| +-------------------------+----+ |
| |
+-------------------------------------------------+
以下是一些Python代码,其中显示了这一点:
orig = [3,1,7,5,9]
print orig
print "====="
list = []
for n in range (len(orig)):
list.append([])
for m in range (len(orig) - n):
list[-1].append([])
for i in range (m+1):
list[-1][-1].append(orig[n+i])
list[-1][-1] = sorted(list[-1][-1])
print "(%d,%d)=%s"%(n,m,list[-1][-1])
print "====="
# Gives xth smallest in index range y through z inclusive.
x = 1; y = 3; z = 4; print "(%d,%d,%d)=%d"%(x,y,z,list[y][z-y][x-1])
x = 2; y = 1; z = 3; print "(%d,%d,%d)=%d"%(x,y,z,list[y][z-y][x-1])
x = 3; y = 0; z = 2; print "(%d,%d,%d)=%d"%(x,y,z,list[y][z-y][x-1])
print "====="
正如所料,输出是:
[3, 1, 7, 5, 9]
=====
(0,0)=[3]
(0,1)=[1, 3]
(0,2)=[1, 3, 7]
(0,3)=[1, 3, 5, 7]
(0,4)=[1, 3, 5, 7, 9]
(1,0)=[1]
(1,1)=[1, 7]
(1,2)=[1, 5, 7]
(1,3)=[1, 5, 7, 9]
(2,0)=[7]
(2,1)=[5, 7]
(2,2)=[5, 7, 9]
(3,0)=[5]
(3,1)=[5, 9]
(4,0)=[9]
=====
(1,3,4)=5
(2,1,3)=5
(3,0,2)=7
=====
答案 1 :(得分:6)
当前解决方案是O((logn)^ 2)。我很确定它可以修改为在O(logn)上运行。该算法优于paxdiablo算法的主要优点是空间效率。该算法需要O(nlogn)空间,而不是O(n ^ 2)空间。
首先,从长度为m和n的两个排序数组中找到第k个最小元素的复杂性是O(logm + logn)。从长度a,b,c,d ..的数组中找到第k个最小元素的复杂度是O(loga + logb + .....)。
现在,对整个数组进行排序并存储它。对数组的前半部分和后半部分进行排序并存储,依此类推。您将有1个排序长度为n的数组,2个排序长度为n / 2的数组,4个排序长度为n / 4的数组,依此类推。所需总内存= 1 * n + 2 * n / 2 + 4 * n / 4 + 8 * n / 8 ... = nlogn。
一旦你有i和j弄清楚子阵列的列表,当连接时,给你范围[i,j]。将有数组的登录号。在其中找到第k个最小数字将花费O((logn)^ 2)时间。
最后一段的示例: 假设数组的大小为8(索引从0到7)。您有以下排序列表:
A:0-7,B:0-3,C:4-7,D:0-1,E:2-3,F:4-5,G:6-7。
现在构造一个带有指向这些数组的指针的树,以便每个节点都包含其直接成分。 A将是root,B和C是其子级等等。
现在实现一个返回数组列表的递归函数。
def getArrays(node, i, j):
if i==node.min and j==node.max:
return [node];
if i<=node.left.max:
if j<=node.left.max:
return [getArrays(node.left, i, j)]; # (i,j) is located within left node
else:
return [ getArrays(node.left, i, node.left.max), getArrays(node.right, node.right.min, j) ]; # (i,j) is spread over left and right node
else:
return [getArrays(node.right, i, j)]; # (i,j) is located within right node
答案 2 :(得分:3)
预处理:创建一个nxn数组,其中[k] [r]元素是第一个r元素的第k个最小元素(为方便起见,为1个索引)。
然后,给定一些特定范围[i,j]和k的值,执行以下操作:
现在,如果我们要求[2,4]范围内的第二个最小值(再次,1个索引),我首先找到[1,4]范围内的第二个最小值,即3.然后看看第1列,看到有1个元素小于或等于3.最后,我发现在[3] [5]时隙的[1,4]范围内的第3个最小值,根据需要为5。
这需要n ^ 2个空格和log(n)查找时间。
答案 3 :(得分:1)
这个不需要预处理,但比O(logN)
慢一些。它比天真的迭代和计数快得多,并且可以支持对序列的动态修改。
就是这样。假设某些n
的{{1}}长度为n=2^x
。构造一个 segment-tree ,其根节点代表x
。对于每个节点,如果它代表节点[0,n-1]
,[a,b]
,则让它有两个子节点,每个节点代表b>a
,[a,(a+b)/2]
。 (也就是说,做一个递归的除以二)。
然后,在每个节点上,为该段中的数字维护单独的二叉搜索树。因此,对序列的每次修改都需要[(a+b)/2+1,b]
查询可以这样完成,让O(logN)[on the segement]*O(logN)[on the BST].
在段Q(a,b,x)
内成为x的 rank 。显然,如果可以有效地计算[a,b]
,Q(a,b,x)
上的二分搜索可以有效地计算所需的答案(使用额外的x
因子。
O(logE)
可以计算为:找到构成Q(a,b,x)
的最小段数,可以在段树上的[a,b]
中完成。对于每个段,在该段的二叉搜索树上查询小于O(logN)
的元素数。添加所有这些数字即可获得x
。
这应该是Q(a,b,x)
。那不完全是你所要求的。
答案 4 :(得分:0)
在O(log n)时间内,无法读取数组的所有元素。由于它没有排序,并且没有其他提供的信息,这是不可能的。
答案 5 :(得分:-1)
在最差和普通情况下,你无法比O(n)做得更好。你必须看看每一个元素。