两个列表中项差的最小和

Minimum summation of difference of terms in two lists

假设我有两个 python 列表,如下所示:

[30, 400, 500]

[55, 396, 478]

我想找到元素之间的最小(绝对值)差之和。在这种情况下很容易:(55-30) + (400-396) + (500-478) = 51

但是,当列表中的元素数量不同时,我将如何有效地执行此操作。例如:

Set 1:

list1 = [30, 400, 500]

list2 = [412, 489]

或者即使是

Set 2

list1 = [30, 400, 500]

list2 = [24, 563]

最后,

Set 3

list1 = [30, 50]

list2 = [20, 31, 90]

对于第 1 组,答案为 (412-400) + (500-489) = 23

对于第 2 组,答案为 (30-24) + (563-500) = 69

对于第 3 组,答案为 (30-20) + (50-31) =29

我无法按元素进行比较。在set 1中,最小差的和是通过比较list1的第二个元素和list2的第一个元素,以及list1的第三个元素和list2的第二个元素来实现的。在集合2中,最小差的和是通过比较list1的第一个元素和list2的第一个元素,以及list1的第三个元素和list2的第二个元素来实现的。

感谢任何帮助。

一些其他信息:

解决这个问题的一种方法是先选择较小的列表。从较小的列表中一个一个地取出数字并搜索最小绝对差(同时跟踪索引),一旦找到最小绝对差,将其添加到最终的 sum 并从较大的列表中删除该元素,以便您不会再考虑了。

这个解决方案是 O(NM)。假设 list1 和 list2 的列表大小约束分别为 N、M。您可以通过对 O(NLogN) 中较大的列表进行排序并使用二进制搜索找到最小绝对差来优化 O(NLogN + NLogM) 的解决方案。

您可以使用 bisect 模块:

import bisect

list1 = [30, 400, 500]
list2 = [412, 489]


list1.sort() # list1 must be sorted

result = []

for el in sorted(list2): # walk through the elements in sorted order
    pos = bisect.bisect_left(list1, el) # find the closest elements
    if pos >= len(list1): # el is bigger than last element, use it
        pos -= 1
    elif pos > 0 and abs(list1[pos-1] - el) <= abs(list1[pos] - el):
        pos = pos - 1
    result.append(abs(list1[pos] - el))
    del list1[pos]

print(result)

结果为 [12, 11](即 [412-400, 500-489]

如果你使用 list2 = [24, 563] 那么你会得到 [6, 63](即 [30-24, 563-500]

如果我理解正确,我相信以下内容应该有效:

list1 = [30, 400, 500]
list2 = [412, 489]

diffs = []
pairs = []
for l2 in list2:
    min_diff = float('inf')
    pair     = None
    for l1 in list1:
        abs_diff = abs(l2-l1)
        if abs_diff < min_diff:
            min_diff = abs_diff
            pair = (l1,l2)
    diffs.append(min_diff)
    pairs.append(pair)

print(diffs)
print(sum(diffs))
print(pairs)

在评论中指出了错误,这里是修改后的版本。

import itertools
def min_abs_diff(l1,l2):
    bigger, smaller = sorted([l1,l2],key=len,reverse=True)
    diffs = [abs(x-y) for x,y in itertools.product(bigger,smaller)]
    return sum(min(diffs[i*len(bigger):(i+1)*len(bigger)]) 
               for i in range(len(diffs)//len(bigger)))

使用排序和压缩。

>>> list1 = [30, 400, 500]
>>> list2 = [412, 489]
>>> l3 = zip(sorted(list1), sorted(list2))
>>> s = 0
>>> for i in l3:
...   s += abs(i[0] - i[1])
...
>>> s
23

如果您仍然需要使用列表中的 "hanging" 值,您可以使用 zip_longestfillvalue 作为默认值来配对悬挂值。然后使用排序,您可以添加 reverse=True 将列表更改为降序。

编辑

有了添加的信息,删除 reverse=True 就差不多了。

为了确保得到正确的答案,我会使用二分加权匹配,其中每对之间的 abs-diff 是权重。这将避免基于排序的方法的所有陷阱,例如

list1=[30, 50], list2=[20, 31, 90], ans= 29

大多数直观算法会将 30 与 31 配对。(总和为 41)

这是一个使用 scipy 的 linear_sum_assignment 的解决方案:

import numpy as np
from scipy.optimize import linear_sum_assignment
def min_diff_sum(list1, list2):
    arr1 = np.asanyarray(list1)
    arr2 = np.asanyarray(list2)
    cost_matrix = np.abs(arr1-arr2[:, None])
    pairs = linear_sum_assignment(cost_matrix)
    return np.sum(cost_matrix[pairs])

这应该总是给出正确的结果。

In [45]: min_diff_sum([30, 400, 500], [412, 489])
Out[45]: 23

In [46]: min_diff_sum([30, 400, 500], [24, 563])
Out[46]: 69

好的,在开始编码之前,这就是我对问题的推理方式: 1. 简单计算所有可能的值。 2.只取出最小的 我不认为任何更复杂的东西都会更有效率,因为最终,您仍然必须测试所有组合以获得完全的确定性。 考虑到这一点,我会这样做:

ll1, ll2 = len(l1), len(l2) 
if ll2 < ll1:
    l1, l2, ll1, ll2 = l2, l1, ll2, ll1
# Now any longer list will be l2 and ll2 >= ll1

在这个阶段,我们需要一个能够将单个列表拆分为列表列表的函数,其中每个子列表(即项目)的长度由指定的数字给出。它们也不能包含相同的项目(来自拆分列表)两次。输入 itertools。

from itertools import combinations, permutations 
# All the lists within l2 that will be mixed with l1 (that is they have same length as l1) :
l2_sublists = combinations(l2, ll1) 
mixes = [l1 + item for item in l2_sublists] 

为了得到每个组合的所有差异总和,我们找到了所有组合;将它们分成两部分;然后对于每个组合求和每个分区中项目差异的绝对值...

diffs = (sum(abs(p[0] - p[1]) for p in (perm[i:i + 2] for i in range(0, len(perm), 2))) for m in mixes for perm in permutations(m, 2 * ll1)) 
result = min(diffs) 
print(result)