给定数组和键,在左右子数组中找到小于或等于自身的元素数量?

时间:2015-03-03 18:15:14

标签: algorithm language-agnostic

我必须找到小于或等于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 )中完成吗?

4 个答案:

答案 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种可能性。第二个数字有两种可能性(01)。第三个数字有三种可能性(012),依此类推。因此,最多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的实现,其中方法Listadd(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的附加字段,它将存储左子树的大小,数字将以最小到最大的顺序插入到我们的树中,每个插入/旋转后更新每个受影响节点的等级字段。我们将在平衡树中允许重复元素。

算法A:
  1. head设置为根节点。将v设置为我们要搜索的值。让i成为v列表中的索引,这将是我们的返回值。
  2. 如果head为空/空,则使用索引i成功返回。
  3. 如果v < head.value,则为head <- head.left,否则为i <- i + head.rank + 1head <- head.right
  4. 回到2。
  5. 原始算法

    对于数组中的每个元素:使用上面描述的算法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