数组中数字的绝对差之和

Posted

技术标签:

【中文标题】数组中数字的绝对差之和【英文标题】:sum of absolute differences of a number in an array 【发布时间】:2014-05-21 13:08:42 【问题描述】:

我想计算索引 i 处的数字与 o(n) 中索引 i-1 之前的所有整数的绝对差之和。但我想不出比 o(n^2) 更好的方法。

例如:

[3,5,6,7,1]

具有绝对和的数组将是(对于索引 i 处的整数,总和将在另一个数组中的索引 i 处):

[0, 2, 4, 7, 17]

谁能帮我将复杂度降低到 o(n)(如果不可能,那么至少在时间复杂度方面有更好的优化)?

这是我的python代码:

a=[3,5,6,7,1]
n=5
absoluteSumArray=[]
for i in range(0,n):
  Sum=0
  for j in range(0,i):
     Sum+=abs(int(a[i])-int(a[j]))
  absoluteSumArray.append(Sum)

【问题讨论】:

[sum( abs(a[i] - a[j]) for j in range(i)) for i in range(n)] yield [0, 2, 4, 7, 17] @cdhagmann 很酷的故事,但 OP 已经有了 O(n^2) 解决方案 “我想计算索引 i 处的数字与索引 i-1 处的所有整数的绝对差之和”。但这不是您的代码正在做的事情。它正在计算索引 i 处的数字与索引 0 到 i-1 处的所有整数的绝对差之和。 O(n) 看起来不太可能。 即使是 strictly easier problem(计算这个问题的输出总和)在几年前出现时也只能得到 O(nlog(n)) 的解决方案。 【参考方案1】:

我可以提供一个 O(n log n) 解决方案作为开始:设 fi 为结果的第 i 个数字。我们有:

当从左到右遍历数组并维护元素a0ai-1的二叉搜索树sub>,我们可以在 O(log n) 中求解公式的所有部分:

保持子树大小以计算大于/小于给定元素的元素 保留累积子树总和以回答大于/小于给定元素的总和查询

如果我们想避免实现成本,我们可以用一些更简单的数据结构替换增强搜索树:

预先对数组进行排序。按排序顺序为每个数字分配其排名 保持 binary indexed tree 的 0/1 值以计算小于给定值的元素数 保留另一个数组值的二叉索引树,以计算小于给定值的元素的总和

TBH 在一般情况下,我认为这不能在 O(n) 中解决。至少你需要在某个时候对数字进行排序。但也许数字是有界的,或者你有其他一些限制,所以你可以在 O(1) 中实现求和和计数操作。

一个实现:

# binary-indexed tree, allows point updates and prefix sum queries
class Fenwick:
  def __init__(self, n):
    self.tree = [0]*(n+1)
    self.n = n
  def update_point(self, i, val):  # O(log n)
    i += 1
    while i <= self.n:
      self.tree[i] += val
      i += i & -i
  def read_prefix(self, i):        # O(log n)
    i += 1
    sum = 0
    while i > 0:
      sum += self.tree[i]
      i -= i & -i
    return sum

def solve(a):
  rank =  v : i for i, v in enumerate(sorted(a)) 
  res = []
  counts, sums = Fenwick(len(a)), Fenwick(len(a))
  total_sum = 0
  for i, x in enumerate(a):
    r = rank[x]
    num_smaller = counts.read_prefix(r)
    sum_smaller = sums.read_prefix(r)
    res.append(total_sum - 2*sum_smaller + x * (2*num_smaller - i))
    counts.update_point(r, 1)
    sums.update_point(r, x)
    total_sum += x
  return res

print(solve([3,5,6,7,1]))  # [0, 2, 4, 7, 17]
print(solve([2,0,1]))      # [0, 2, 2]

【讨论】:

你太擅长算法了:P ...很好的答案...而且你说服了我唯一正确的人 我认为 o(n) 是不可能的。但是在我实现这个时,我会稍等一下(以防万一)。谢谢。 @JoranBeasley:我没有看到证明它有效(似乎缺少一个因素)并且没有代码可以尝试 @JoranBeasley 什么?它是 O(n log n),OPs 版本是 Omega(n^2)。显然,小列表的常数因子要高得多。尝试使用大小为 10000 的列表,您会非常清楚地注意到差异 在大小为 10k 的列表上,我看到这个算法的时间为 0.065(1 次迭代),而他的旧算法 @ 15.115(也只有 1 次)......这是一个非常棒的加速(如果您的列表很大)【参考方案2】:

这是线性决策树模型中的Omega(n log n)-comparison 下限。这排除了“不错的”o(n log n)-time 算法的可能性(两个现已删除的答案都在这个类中)。

从计算问题可以简单地减少这个问题

f(x1, ..., xn) = sum_i sum_j |xi - xj|.

函数fx1, ..., xn 是完全可微的当且仅当x1, ..., xn 成对不同。 f 完全可微的集合因此具有n! 连通分量,其中决策树的每个叶子最多可以处理一个。

【讨论】:

是时候学习一下线性决策树的功能了,这样我才能理解这个界限。

以上是关于数组中数字的绝对差之和的主要内容,如果未能解决你的问题,请参考以下文章

获得两个双精度数组绝对差之和的有效方法

绝对差不超过限制的最长数组

找到给定范围内数字的最大最小差

不能由数组中的数字之和形成的最小数字

有这样一个数组A,大小为n,相邻元素差的绝对值都是1。

快速找出一个数组中的两个数字,让这两个数字之和等于一个给定的值