【问题标题】:How do I pick elements from two arrays such that their sum is minimum?如何从两个数组中选择元素以使它们的总和最小?
【发布时间】:2019-06-27 13:49:46
【问题描述】:

我有两个用整数填充的等长数组(可以是正数或负数,但从不为 0)。在每个索引处,我可以选择 array1 的元素,也可以从 array2 中选择元素,并且这些元素之和的绝对值应该是最小的。

例如:

a1 = [2, 2, 1]
a2 = [-3, -3, -4]

正确的答案是这样选择:

At index 0 : -3 from a2
At index 1 : 2 from a1
At index 2 : 1 from a1

因此,最终总和为 0。

【问题讨论】:

  • 那你的方法是什么?
  • 您可以自上而下或自下而上进行。
  • 我的方法是从两个数组中获取具有最小绝对值的元素,这适用于很多情况,但在我在问题中显示的示例中失败。这可以在没有 dp 的情况下完成吗?
  • Can this be done without dp?可能(也许在空间上有所改善)。但是,您能否举出更多示例,因为absoulute value of the sum 对我来说有点不清楚。
  • 当然,a1 = [2, 1, 1, -1] a2 = [-1, -2, -2, -4] ans = [-1, 1, 1, -1]顺便说一句,我想最小化的是 pos + abs(neg + pos)。这里,pos 和 neg 是从两个原始元素中挑选出理想元素后形成的最终数组的正负值之和。这就是为什么我选择 -3、2、1 而不是 2、2、-4。

标签: arrays algorithm sorting dynamic-programming greedy


【解决方案1】:

首先,简化问题:

  • 创建数组b,其中b[i] = a1[i] - a2[i]
  • sumA1 = a1 中每个元素的总和。

那么问题就变成了:

b中找到一个子数组,标记为c,将其和标记为sumC,它应该最接近sumA1

或者,你也可以说它应该有最少的Math.abs(sumC - sumA1)

顺便说一句,如果c 为空,它也是有效的,这意味着从a1 中选择所有索引。

那么这个问题和这个类似:Given an input array find all subarrays with given sum K

或者,参考这篇文章:

然后,回到 OP 的问题:

  • b 中选择的任何索引都用于a2
  • b 中未选择的任何索引都用于a1

【讨论】:

【解决方案2】:

这是一个动态编程解决方案,它找到pos + abs(neg + pos) 的最小值(根据 OP 的更新)并打印一个候选解决方案。我们需要将总和和正整数的总和保存为 dp 状态以找到最小值。我不确定我们是否可以在没有pos 维度的情况下解决它。时间复杂度为O(#elements * (sum of absolute values of elements)^2)。当然,如果个别数字非常大,这不是一个可行的解决方案。在这种情况下,当元素数量为 ~20 时,蛮力方法将起作用。

a1 = [2, 1, 1, -1] 
a2 = [-1, -2, -2, -4]
memo = {}   # to store dp state
nxt = {}    # for reconstructing path

def foo(a1, a2, index, total, pos):
    if index == len(a1):
        return pos + abs(total)
    if (index, total, pos) in memo:
        return memo[(index, total, pos)]

    # take from first array
    if a1[index] > 0:
        r1 = foo(a1, a2, index+1, total + a1[index], pos+a1[index])
    else:
        r1 = foo(a1, a2, index+1, total + a1[index], pos)

    # take from second array
    if a2[index] > 0:
        r2 = foo(a1, a2, index+1, total + a2[index], pos+a2[index])
    else:
        r2 = foo(a1, a2, index+1, total + a2[index], pos)

    # save path taken at this step
    if r1 < r2:
        nxt[index] = 0
    else:
        nxt[index] = 1

    memo[index, total, pos] = min(r1, r2)
    return min(r1, r2)

print('minimum sum:', foo(a1, a2, 0, 0, 0))   # minimum sum: 2
# path reconstruction
path = []
node = 0
while node < len(a1):
    path.append(nxt[node])
    node += 1
print('path:', path)   # path: [1, 0, 0, 0]

【讨论】:

  • 这样肯定更有效率。谢谢!
【解决方案3】:
import itertools as iter
a = [a1, a2]
p = len(a1)
idx_to_pick = min(iter.product(*([[0, 1]]*p)), 
                  key=lambda b: abs(sum([a[i][j] for i, j in zip(b, range(p))])))

此代码建议选择a1[0] + a1[1] + a2[2] = 2 + 2 + (-4),与OP的选择不同,但也是正确的。

更新每个 OP 的后续问题对此答案的评论:

import itertools as iter
a1 = [2, 2, 1]
a2 = [-3, -3, -4]
a = [a1, a2]
p = len(a1)


def obj_func(b):
    arr = [a[i][j] for i, j in zip(b, range(p))]
    return sum([x for x in arr if x > 0]) + abs(sum(arr))


idx_to_pick = min(iter.product(*([[0, 1]]*p)), key=obj_func)

有了新的目标函数,仍然有多种解决方案。可以是 (-3, 2, 1) 或 (2, -3, 1)

【讨论】:

  • 这基本上是蛮力,不是吗?因为我不介意找到一个蛮力解决方案,你能帮我把它扩展到我想最小化这个值的地方吗:pos + abs(neg + pos) 这里,pos 和 neg 是正负值的总和从两个原始元素中选择理想元素后将形成的最终数组。这就是为什么我选择 -3、2、1 而不是 2、2、-4。
  • 当然,要清楚,基本上你要最小化的是sum(all the positive values in the final array) + abs(sum(all values in array))?
  • @DistinctlyAverage,0 呢,它算作 pos 还是 neg?如果两者都不是,则 neg+pos 不等于所有的总和。
  • @Indominus 问题陈述说could be positive or negative but never 0
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-06-20
  • 1970-01-01
  • 1970-01-01
  • 2015-09-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多