使用Python,找到数字组合,使它们的总和尽可能接近另一个给定的数字。

3
问题:
1. 我需要在array2中找到三个数字,使它们的和等于或尽可能接近array3中的每个数字(必须是三个数字)。 2. 打印出使用array2中每个数字对应的list1中的索引。 3. 只能使用array2中的每个数字两次。
数据:我有一个列表和两个数组的三个数据集。第一个列表是名称,第二个数组是数字,第三个数组是目标。list1和array2的长度相同(55),但array3的长度不同。
list1 = ['ab', 'ac', 'ad', 'ae', 'af', 'ag', 'ah', 'ai', 'aj', 'ak', 
'bc', 'bd', 'be', 'bf', 'bg', 'bh', 'bi', 'bj', 'bk', 'cd', 'ce', 
'cf', 'cg', 'ch', 'ci', 'cj', 'ck', 'de', 'df', 'dg', 'dh', 'di', 
'dj', 'dk', 'ef', 'eg', 'eh', 'ei', 'ej', 'ek', 'fg', 'fh', 'fi', 
'fj', 'fk', 'gh', 'gi', 'gj', 'gk', 'hi', 'hj', 'hk', 'ij', 'ik', 
'jk']
array2 = [39, 6, 29, 38, 2, 34, 7, 6, 2, 3, 37, 13, 20, 18, 4, 14, 
28, 2, 20, 25, 13, 38, 32, 28, 9, 7, 14, 11, 31, 29, 29, 39, 9, 35, 
14, 34, 23, 31, 11, 2, 37, 19, 18, 6, 5, 12, 6, 33, 30, 22, 38, 37, 
13, 31, 40]
array3 = [80, 74, 84, 89, 89, 78, 79, 85, 81, 89, 75, 86, 76, 71, 
82, 79, 75, 78, 83, 89]

我所需要的结果如下:
对于数组array3中的80,使用39+38+3,从list1中获取“ab”,“ae”和“ak”。
对于数组array3中的74,使用39+32+2,从list1中获取“ab”,“cg”和“ek”。
我正在尝试找到一种Pythonic方式来解决这个问题,使用Python 3.x。我已经研究过itertools.combinations/permutations和背包问题。背包问题最接近解决此问题,但需要评估两个值以获得针对目标的最佳解决方案,而我只需要一个。
我不是在寻找某人为我编写代码(如果你想的话,我也不会阻止你),而是在寻找比我更有经验的人指导我解决这个问题的方向。

1
我正在尝试找到一种Pythonic的解决方法。乍一看,这个问题可能足够小,可以在合理的时间内通过暴力破解来解决(我还没有计算出复杂度)。如果更大的话,就别想了,你需要使用启发式算法。 - roganjosh
有任何尺寸限制吗?如果您不担心超过100个数字计数,那么这就相当容易。否则,这是一个算法问题。 - Rocky Li
2
你目前尝试了什么?给我们展示一些代码吧! - Klaus D.
array2 中的数字可以为每个 array3 中的数字使用两次,还是总共只能使用两次?后者比较复杂。 - Davis Herring
@DavisHerring array2中的数字每个只能在array3中使用一次,且总共只能用两次。 @RockyLi 不幸的是,array3中的计数可以超过100,但我想知道您如何解决这个问题。 @KlausD. 我尝试过修改背包函数来仅使用“价值”或“重量”之一,但问题在于它需要一个“值”和一个“重量”才能工作。这让我觉得我可能在错误的方向上解决问题,因此我在这里向聪明的您寻求解决方法的建议。 - imclallen
2个回答

1
这假设数组2中每个元素(具有不同的索引)只使用一次(您可以扩展到元素重复),并且您不关心使用哪三个元素:
# target is the desired number from array3
def triplet_sum(list1, array2, target):
    n = len(array2)
    a = [(i, array2[i]) for i in range(n)]
    a.sort(key=lambda x: x[1])
    j = 1
    i = j-1
    k = j+1
    best_sum = sys.maxsize
    best_answer = None
    while j < n:
        while i >= 0 and k < n:
            x = a[i][1]
            y = a[j][1]
            z = a[k][1]
            S = x + y + z
            candidate = [(x, list1[a[i][0]]), (y, list1[a[j][0]]), (z, list1[a[k][0]])]
            if S == target:
                return candidate
            elif S > target:
                i -= 1
            else:
                k += 1
            if abs(target - best_sum) > abs(target - S):
                best_sum = S
                best_answer = candidate
        j += 1
        i = j-1
        k = j+1
    return best_answer

示例输出:

# Closest match
triplet_sum(list1, array2, 5)
[(2, 'af'), (2, 'aj'), (2, 'bj')]
# An exact match
triplet_sum(list1, array2, 80)
[(20, 'be'), (20, 'bk'), (40, 'jk')]

我只是沿着已排序的列表a移动我的中间选择j,如果S太高,就向左移动i,如果S太低,就向右移动k。一眼看去复杂度为O(n^2)


1
以下算法在所有目标(存储在array3中)的所有三元组中,搜索在array2中的解。
list1 = ['ab', 'ac', 'ad', 'ae', 'af', 'ag', 'ah', 'ai', 'aj', 'ak', 'bc', 'bd', 'be', 'bf', 'bg', 'bh', 'bi', 'bj', 'bk', 'cd', 'ce', 'cf', 'cg', 'ch', 'ci', 'cj', 'ck', 'de', 'df', 'dg', 'dh', 'di', 'dj', 'dk', 'ef', 'eg', 'eh', 'ei', 'ej', 'ek', 'fg', 'fh', 'fi', 'fj', 'fk', 'gh', 'gi', 'gj', 'gk', 'hi', 'hj', 'hk', 'ij', 'ik', 'jk']
array2 = [39, 6, 29, 38, 2, 34, 7, 6, 2, 3, 37, 13, 20, 18, 4, 14, 28, 2, 20, 25, 13, 38, 32, 28, 9, 7, 14, 11, 31, 29, 29, 39, 9, 35, 14, 34, 23, 31, 11, 2, 37, 19, 18, 6, 5, 12, 6, 33, 30, 22, 38, 37, 13, 31, 40]
array3 = [80, 74, 84, 89, 89, 78, 79, 85, 81, 89, 75, 86, 76, 71, 82, 79, 75, 78, 83, 89]

import itertools
import numpy as np
import heapq
import copy

list1 = np.array(list1, dtype=str)
array2 = np.array(array2, dtype=int)
array3 = np.array(array3, dtype=int)

m, n = len(array2), len(array3)

combs = [[] for __ in range(n)]

maxuses = 2

combinations = set(map(tuple, itertools.combinations(list(range(m))*maxuses, 3)))
print(f'searching in {len(combinations)}! space')

def dist(a, b):
    return abs(a - b)

for i, target in enumerate(array3):
    for comb in map(list, combinations):
        combs[i].append((dist(target, sum(array2[comb])), comb))

    combs[i].sort(key=lambda item: item[0])

tested = set()

cost = 0
locs = [0]*n
used = {i: [] for i in range(m)}

for i in range(n):
    for value in combs[i][0][1]:
        used[value].append(i)
    cost += combs[i][0][0]

def priority(values):
    return (np.array(list(map(len, values)))**2).sum()

minheap = [(cost, priority(used.values()), locs, used)]

count = 0
while minheap:
    cost, __, locs, used = heapq.heappop(minheap)

    count += 1
    print(f'tested {count}, best cost {cost}, heap size {len(minheap)}')

    for key in used:
        if len(used[key]) > maxuses:
            loc1 = used[key][-1]
            loc2 = next(itertools.islice(filter(lambda x: x != loc1, used[key]), 0, None))

            print(f'value at {key} is used by {len(used[key])} combinations')

            # print(key, used[key])
            # print(loc1, combs[loc1][locs[loc1]][1])
            # print(loc2, combs[loc2][locs[loc2]][1])
            for value in combs[loc1][locs[loc1]][1]:
                used[value].remove(loc1)
            for value in combs[loc2][locs[loc2]][1]:
                used[value].remove(loc2)

            if loc1 < len(combinations)-1:
                cost1 = cost
                locs1 = list(locs)
                used1 = copy.deepcopy(used)

                cost1 -= combs[loc1][locs[loc1]][0]
                locs1[loc1] += 1
                cost1 += combs[loc1][locs[loc1]][0]

                for value in combs[loc1][locs1[loc1]][1]:
                    used1[value].append(loc1)
                for value in combs[loc2][locs1[loc2]][1]:
                    used1[value].append(loc2)

                if tuple(locs1) not in tested:
                    tested.add(tuple(locs1))
                    heapq.heappush(minheap, (cost1, priority(used1.values()), locs1, used1))

            if loc2 < len(combinations)-1:
                cost2 = cost
                locs2 = list(locs)
                used2 = copy.deepcopy(used)

                cost2 -= combs[loc2][locs2[loc2]][0]
                locs2[loc2] += 1
                cost2 += combs[loc2][locs2[loc2]][0]

                for value in combs[loc1][locs2[loc1]][1]:
                    used2[value].append(loc1)
                for value in combs[loc2][locs2[loc2]][1]:
                    used2[value].append(loc2)

                if tuple(locs2) not in tested:
                    tested.add(tuple(locs2))
                    heapq.heappush(minheap, (cost2, priority(used2.values()), locs2, used2))
            break
    else:
        print(f'found a solution with {cost} cost:')
        print(locs)

        for i , target in enumerate(array3):
            print(f'{target}\t~=\t ', end='')
            print(*array2[combs[i][locs[i]][1]], sep='+', end=' ')
            print('\t(', end='')
            print(*list1[combs[i][locs[i]][1]], sep=', ', end='')
            print(')')

        exit()

它将返回最小化成本并且仅使用array2中的每个数字不超过两次的三元组组合之一。
因为您没有指定在没有确切解的情况下最佳解决方案的标准,所以我假设了三元组的总和与其目标之间的绝对差异,但您可以在dist中更改该值。
它在您的示例中运行非常快(<10s),但我不能保证它会像那样快,并且您可能需要一些随机化。但这是您示例的一个解决方案:
80      ~=       28+23+29       (ch, eh, dg)
74      ~=       29+39+6        (dg, di, ai)
84      ~=       13+33+38       (ij, gj, hj)
89      ~=       37+39+13       (bc, di, ij)
89      ~=       30+40+19       (gk, jk, fh)
78      ~=       7+40+31        (ah, jk, ei)
79      ~=       31+18+30       (ei, fi, gk)
85      ~=       13+37+35       (ce, fg, dk)
81      ~=       18+32+31       (bf, cg, df)
89      ~=       34+20+35       (eg, be, dk)
75      ~=       13+28+34       (bd, bi, ag)
86      ~=       18+39+29       (bf, ab, dh)
76      ~=       29+38+9        (ad, hj, dj)
71      ~=       14+37+20       (bh, bc, be)
82      ~=       29+20+33       (dh, bk, gj)
79      ~=       14+37+28       (ef, hk, ch)
75      ~=       28+9+38        (bi, ci, ae)
78      ~=       34+38+6        (eg, cf, gi)
83      ~=       29+31+23       (ad, df, eh)
89      ~=       37+38+14       (hk, cf, ef)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接