我必须找到小于或等于i
的元素数量 - 左右子阵列中数组的元素。
例如,如果我的数组是
A[]=4 3 5 2 2 5
我的2个数组将是
0 0 2 0 0 5
并且
3 2 3 1 1 0
第一个数组的i
- 元素表示小于或等于i
的元素个数 - 第i
个元素左侧的元素。
第二个数组的i
- 元素表示小于或等于i
的元素个数 - 第i
个元素右侧的元素。
我可以使用两个循环在 O ( n 2 )中找到这些数组。
可以在 O ( n )中完成吗?
答案 0 :(得分:5)
您可以使用Fenwick Tree在O(nlogm)(其中n是A的长度,m是数组中最大元素的大小)中执行此操作。
Fenwick树(也称为二进制索引树)允许您向数组添加元素,并在O(logn)时间内计算连续元素的总和。有一个很好的教程on topcoder。
在这个问题中,我们可以使用Fenwick树来存储我们看到每个值的次数的直方图。直方图开始为空,然后我们逐渐将元素插入直方图。
所以我们需要遍历数组,每次首先计算有多少元素的值小于当前值,然后将当前值添加到数组中。
Python代码:
def find_lower(A):
"""Return B where B[i] is the number of elements <= A[i] in A[0:i]"""
top = max(A) + 1
F = fenwick_new(top)
left = []
for a in A:
left.append( fenwick_sum(F,a) )
fenwick_increase(F,a,1)
return left
A=[4, 3, 5, 2, 2, 5]
print find_lower(A)
print find_lower(A[::-1])[::-1]
这使用了一些标准的Fenwick树函数:
def fenwick_new(m):
"""Create empty fenwick tree with space for elements in range 0..m"""
# tree[i] is sum of elements with indexes i&(i+1)..i inclusive
return [0] * (m+1)
def fenwick_increase(tree,i,delta):
"""Increase value of i-th element in tree by delta"""
while i < len(tree):
tree[i] += delta
i |= i + 1
def fenwick_sum(tree,i):
"""Return sum of elements 0..i inclusive in tree"""
s = 0
while i >= 0:
s += tree[i]
i &= i + 1
i -= 1
return s
答案 1 :(得分:3)
不,不可能在O(n)
时间内完成。你能做的最好的是O(n log n)
。
这是证明。假设原始数组包含n
不同的元素。让我们弄清楚你想要的第一个阵列有多少种可能性(另一个阵列的证明是相似的)。第一个数字(0
)有1种可能性。第二个数字有两种可能性(0
或1
)。第三个数字有三种可能性(0
,1
或2
),依此类推。因此,最多1 * 2 * 3 * ... * n = n!
个可能的答案。实际上很容易看出,对于这些可能的答案中的每一个,至少有一个不同数字的数组产生它(从左到右工作。如果answer[i]
是0
,则设置{ {1}}小于之前选择的所有数字。如果original[i]
为answer[i]
,请将i
设置为大于之前选择的所有数字。否则,设置original[i]
}是一个在original[i]
- 最小和i
之间已经选择的最小数字之间的数字。任何算法都必须确定哪个(i+1)
可能的答案是正确的答案。如果我们总是可以使用n!
比较来完成算法,则遵循f(n)
(每个比较只有2个可能的结果,因为原始数字都是不同的)。记录双方的日志(基数2),得到2^f(n) >= n!
。使用斯特林对f(n) >= log(n!)
的近似,我们发现我们不能比log(n!)
比较做得更好。
可以在O(n log n)
时间内完成。以下Java方法(不在O(n log n)
时间内运行)返回您需要的第一个数组(第二个数组可以类似地完成)。 O(n log n)
使用Arrays.sort()
的TimSort。但是,为了使整个方法在O(n log n)
时间运行,您必须将O(n log n)
替换为ArrayList
的实现,其中方法List
,add(Object object)
并且remove(int index)
都在lastIndexOf(Object object)
时间内运行。根据{{3}},O(log n)
在AvlTreeList
时间内add()
和remove()
,并且可能会增加&#34;一个O(log n)
,以便搜索AvlTreeList
。这证明您的数组可以在O(log n)
时间内找到。
O(n log n)
答案 2 :(得分:1)
我将使用名为 平衡二叉树和排名字段 的东西来简化@ pbabcdefp的答案(参见Knuth 6.2.3,线性列表表示)。考虑一个平衡的树(实现无关紧要,所以红黑或AVL都可以正常工作)但我们还有一个名为rank
的附加字段,它将存储左子树的大小,数字将以最小到最大的顺序插入到我们的树中,每个插入/旋转后更新每个受影响节点的等级字段。我们将在平衡树中允许重复元素。
head
设置为根节点。将v
设置为我们要搜索的值。让i
成为v
列表中的索引,这将是我们的返回值。head
为空/空,则使用索引i
成功返回。v < head.value
,则为head <- head.left
,否则为i <- i + head.rank + 1
和head <- head.right
。对于数组中的每个元素:使用上面描述的算法A来查找元素的数量(在树中,因此在当前元素之前)小于或等于它,然后使用它将它添加到我们的树中修改插入。这给出了第一个数组。重复一次,这次是向后走过阵列而不是向前走,以获得第二个阵列。
答案 3 :(得分:0)
似乎你无法跳过nlogn所以基本版本只是:
def naive(arr)
res = Array.new(arr.size,0)
for i in 0..arr.length-1
for j in i+1..arr.length-1
res[i] += 1 if arr[i] >= arr[j]
end
end
res
end
但是,根据您的数据集,您可以进行一些优化。例如,在您的示例中有重复 - 我们可以使用该事实来避免两次遍历相同数字的数组。在这种情况下,不是搜索小于当前的数字 - 你可以对每个大于当前的数字(从右到左)添加,并忽略已经使用过的数字:
def rightleft(arr)
ignore = {}
res = Array.new(arr.size,0)
(arr.size-1).downto(0) do |i|
current = arr[i]
next if ignore[current]
additor = 1
(i-1).downto(0) do |j|
if arr[i] <= arr[j]
res[j] += additor
if arr[i] == arr[j]
additor += 1
ignore[current] = true
end
end
end
end
res
end
让我们举一些重复的例子:
A = %W{1 2 2 3 3 5 4 6 5 7 8 9 10 11 12 14 5 6 4 3 1 7 8 9 3 5 4 2 2 3 3 5 4 6 5 7 8 9 10 11 12 14 5 6 4 3 1 7 8 9 3 5 4 6 5 7 8 9 10 11 12 14 5 6 4 3 1 10 2 11 12 4 13 1 2 2 3 3 5 4 12 14 5 6 4 3 1 7 8 9 3 5 4 6 5 7 8 9 10 11 12 14 5 6 4 3 1 10 2 11 12 4 13 1 2 2 3 3 5 4 6 5 7 8 9 10 11 12 14 6 5 7 8 9 10 11 12 14 5 6 4 3 1 10 2 11 12 4 13 1 2 2 3 3 5 4 12 14 5 6 4 3 1 7 8 9 3 5 4 6 5 7 8 9 10 11 12 14 5 6 4 3 1 10 2 11 12 4 13 1 2 2 3 3 5 4 6 5 7 8 9 10 11 12 14 5 6 4 3 1 7 8 9 1 10 2 11 12 4 13 14 6 15 12 16 17 18 19}.map(&:to_i)
现在基准
puts "NAIVE 100 times:"
puts time_elapsed{
100.times do
naive(A)
end
}
puts "rl 100 times"
puts time_elapsed{
100.times do
rightleft(A)
end
}
结果:
NAIVE 100 times:
[14, 30, 29, 53,...., 0, 0]
Time elapsed 485.935997 milliseconds
rl 100 times
[14, 30, 29, 53,...., 0, 0]
Time elapsed 81.735048 milliseconds
但是当你没有重复时 - 这种优化会使它稍慢。以下是数字1,2,3 ...,99,100改组的结果:
NAIVE 100 times:
[70, 7,... 1, 0]
Time elapsed 99.58762899999999 milliseconds
rl 100 times
[70, 7, ... 1, 0]
Time elapsed 113.186392 milliseconds