繁体   English   中英

查找列表中哪个数字总和等于某个数字的算法

[英]Algorithm to find which number in a list sum up to a certain number

我有一个数字列表。 我也有一定数额。 总和是由我列表中的几个数字组成的(我可能/可能不知道它是由多少个数字组成的)。 是否有一种快速算法来获取可能的数字列表? 用 Python 编写会很棒,但伪代码也很好。 (除了 Python 之外,我还无法阅读其他任何内容:P)

例子

list = [1,2,3,10]
sum = 12
result = [2,10]

注意:我确实知道从大小为 n 的列表中找到哪些数字总和到另一个数字的算法(但我无法阅读 C#,我无法检查它是否适合我的需要。我在 Linux 上,我尝试使用Mono,但我收到错误,我无法弄清楚如何使用 C# :(
而且我确实知道算法可以总结所有组合的数字列表(但它似乎效率很低。我不需要所有组合。)

这个问题简化为0-1 背包问题,您试图找到一个具有精确总和的集合。 解决方案取决于约束,在一般情况下,这个问题是 NP-Complete。

但是,如果最大搜索和(我们称之为S )不太高,那么您可以使用动态规划来解决问题。 我将使用递归函数和memoization来解释它,这比自下而上的方法更容易理解。

让我们编写一个函数f(v, i, S) ,以便它返回v[i:]中的子集数,其总和正好为S 要递归求解,首先我们要分析基数(即: v[i:]为空):

  • S == 0: []的唯一子集总和为 0,因此它是有效子集。 因此,该函数应返回 1。

  • S != 0:由于[]的唯一子集总和为 0,因此不存在有效子集。 因此,该函数应返回 0。

然后,我们来分析递归的情况(即: v[i:]不为空)。 有两种选择:在当前子集中包含数字v[i] ,或者不包含它。 如果我们包含v[i] ,那么我们正在寻找总和S - v[i]的子集,否则,我们仍在寻找总和S的子集。 函数f可以通过以下方式实现:

def f(v, i, S):
  if i >= len(v): return 1 if S == 0 else 0
  count = f(v, i + 1, S)
  count += f(v, i + 1, S - v[i])
  return count

v = [1, 2, 3, 10]
sum = 12
print(f(v, 0, sum))

通过检查f(v, 0, S) > 0 ,您可以知道您的问题是否有解决方案。 但是,这段代码太慢了,每个递归调用都会产生两个新调用,这导致了 O(2^n) 算法。 现在,我们可以应用memoization让它在 O(n*S) 时间内运行,如果S不太大,这会更快:

def f(v, i, S, memo):
  if i >= len(v): return 1 if S == 0 else 0
  if (i, S) not in memo:  # <-- Check if value has not been calculated.
    count = f(v, i + 1, S, memo)
    count += f(v, i + 1, S - v[i], memo)
    memo[(i, S)] = count  # <-- Memoize calculated result.
  return memo[(i, S)]     # <-- Return memoized value.

v = [1, 2, 3, 10]
sum = 12
memo = dict()
print(f(v, 0, sum, memo))

现在,可以编写一个函数g来返回一个对S求和的子集。 为此,仅当至少有一个解决方案包含元素时才添加元素:

def f(v, i, S, memo):
  # ... same as before ...

def g(v, S, memo):
  subset = []
  for i, x in enumerate(v):
    # Check if there is still a solution if we include v[i]
    if f(v, i + 1, S - x, memo) > 0:
      subset.append(x)
      S -= x
  return subset

v = [1, 2, 3, 10]
sum = 12
memo = dict()
if f(v, 0, sum, memo) == 0: print("There are no valid subsets.")
else: print(g(v, sum, memo))

免责声明:该解决方案表示 [10, 10] 的两个子集总和为 10。这是因为它假设前十个与后十个不同。 该算法可以固定为假设两个十位相等(因此回答一个),但这有点复杂。

我知道自从你问这个问题 10 年后我会给出答案,但我真的需要知道如何做到这一点,而 jbernadas 的方式对我来说太难了,所以我用谷歌搜索了一个小时,我找到了一条蟒蛇完成工作的库itertools

我希望这对未来的新手程序员有所帮助。 您只需要导入库并使用.combinations()方法,就这么简单,它按顺序返回集合中的所有子集,我的意思是:

对于集合[1, 2, 3, 4]和长度为 3 的子集,它不会返回[1, 2, 3][1, 3, 2][2, 3, 1]它只会返回 [1, 2, 3]

当你想要一个集合的所有子集时,你可以迭代它:

import itertools

sequence = [1, 2, 3, 4]
for i in range(len(sequence)):
    for j in itertools.combinations(sequence, i):
        print(j)

输出将是

() (1,) (​​2,) (3,) (4,) (1, 2) (1, 3) (1, 4) (2, 3) (2, 4) (3, 4) (1 , 2, 3) (1, 2, 4) (1, 3, 4) (2, 3, 4)

希望这有帮助!

所以,逻辑是对数字进行反向排序,假设数字列表是l并且要形成的总和是s

   for i in b:
            if(a(round(n-i,2),b[b.index(i)+1:])):
                r.append(i)    
                return True
        return False

然后,我们通过这个循环,从l中按顺序选择一个数字,假设它是i 有两种可能的情况,是否是 sum 的一部分。 因此,我们假设i是解决方案的一部分,然后问题简化为ll[l.index(i+1):]并且ssi所以,如果我们的函数是 a(l,s) 那么我们称a(l[l.index(i+1):] ,si) 如果i不是s的一部分,那么我们必须从l[l.index(i+1):]列表中形成s 所以在这两种情况下都是相似的,唯一的变化是如果 i 是 s 的一部分,那么 s=si 否则只有 s=s。

现在要减少问题,如果 l 中的数字大于 s,我们会删除它们以降低复杂性,直到 l 为空,在这种情况下,选择的数字不是我们解决方案的一部分,我们返回 false。

if(len(b)==0):
    return False    
while(b[0]>n):
    b.remove(b[0])
    if(len(b)==0):
        return False    

如果 l 只剩下 1 个元素,那么它可以是 s 的一部分,那么我们返回 true 或者不是,那么我们返回 false 并且循环将遍历其他数字。

if(b[0]==n):
    r.append(b[0])
    return True
if(len(b)==1):
    return False

请注意循环中是否使用了 b..但 b 只是我们的列表。并且我已尽可能四舍五入,因此我们不应该因为 python 中的浮点计算而得到错误的答案。

r=[]
list_of_numbers=[61.12,13.11,100.12,12.32,200,60.00,145.34,14.22,100.21,14.77,214.35,200.32,65.43,0.49,132.13,143.21,156.34,11.32,12.34,15.67,17.89,21.23,14.21,12,122,134]
list_of_numbers=sorted(list_of_numbers)
list_of_numbers.reverse()
sum_to_be_formed=401.54
def a(n,b):
    global r
    if(len(b)==0):
        return False    
    while(b[0]>n):
        b.remove(b[0])
        if(len(b)==0):
            return False    
    if(b[0]==n):
        r.append(b[0])
        return True
    if(len(b)==1):
        return False
    for i in b:
        if(a(round(n-i,2),b[b.index(i)+1:])):
            r.append(i)    
            return True
    return False
if(a(sum_to_be_formed,list_of_numbers)):
    print(r)

这个解决方案工作得很快。比上面解释的更快。 但是,这仅适用于正数。 但是,如果只有解决方案,它也很有效,否则需要很长时间才能摆脱循环。

一个示例运行是这样的让我们说

    l=[1,6,7,8,10]

and s=22 i.e. s=1+6+7+8
so it goes through like this 

1.) [10, 8, 7, 6, 1] 22
i.e. 10  is selected to be part of 22..so s=22-10=12 and l=l.remove(10)
2.) [8, 7, 6, 1] 12
i.e. 8  is selected to be part of 12..so s=12-8=4 and l=l.remove(8)
3.) [7, 6, 1] 4  
now 7,6 are removed and 1!=4 so it will return false for this execution where 8 is selected.
4.)[6, 1] 5
i.e. 7  is selected to be part of 12..so s=12-7=5 and l=l.remove(7)
now 6 are removed and 1!=5 so it will return false for this execution where 7 is selected.
5.)[1] 6
i.e. 6  is selected to be part of 12..so s=12-6=6 and l=l.remove(6)
now 1!=6 so it will return false for this execution where 6 is selected.
6.)[] 11
i.e. 1 is selected to be part of 12..so s=12-1=1 and l=l.remove(1)
now l is empty so all the cases for which 10 was a part of s are false and so 10 is not a part of s and we now start with 8 and same cases follow.
7.)[7, 6, 1] 14
8.)[6, 1] 7
9.)[1] 1

只是为了比较一下我在我的电脑上运行的不太好。 使用

l=[61.12,13.11,100.12,12.32,200,60.00,145.34,14.22,100.21,14.77,214.35,145.21,123.56,11.90,200.32,65.43,0.49,132.13,143.21,156.34,11.32,12.34,15.67,17.89,21.23,14.21,12,122,134]

s=2000

我的循环运行了 1018 次和 31 毫秒。

之前的代码循环运行了 3415587 次,耗时近 16 秒。

但是,如果解决方案不存在,我的代码运行了几分钟以上,所以我停止了它,之前的代码仅在 17 毫秒左右运行,并且之前的代码也适用于负数。

所以我觉得可以做一些改进。

#!/usr/bin/python2

ylist = [1, 2, 3, 4, 5, 6, 7, 9, 2, 5, 3, -1]
print ylist 
target = int(raw_input("enter the target number")) 
for i in xrange(len(ylist)):
    sno = target-ylist[i]
    for j in xrange(i+1, len(ylist)):
        if ylist[j] == sno:
            print ylist[i], ylist[j]

这个python代码按照你的要求做,它会打印出唯一的一对数字,其总和等于目标变量。

if target number is 8, it will print: 
1 7
2 6
3 5
3 5
5 3
6 2
9 -1
5 3

我找到了一个答案,它的运行时间复杂度为 O(n),空间复杂度约为 O(2n),其中 n 是列表的长度。

答案满足以下约束:

  1. 列表可以包含重复项,例如 [1,1,1,2,3] 并且您想找到对总和为 2

  2. 列表可以包含正整数和负整数

代码如下,后面是解释:

def countPairs(k, a):
    # List a, sum is k
    temp = dict()
    count = 0
    for iter1 in a:
        temp[iter1] = 0
        temp[k-iter1] = 0
    for iter2 in a:
        temp[iter2] += 1
    for iter3 in list(temp.keys()):
        if iter3 == k / 2 and temp[iter3] > 1:
            count += temp[iter3] * (temp[k-iter3] - 1) / 2
        elif iter3 == k / 2 and temp[iter3] <= 1:
            continue
        else:
            count += temp[iter3] * temp[k-iter3] / 2
    return int(count)
  1. 创建一个空字典,遍历列表并将所有可能的键放入初始值为 0 的字典中。请注意,键 (k-iter1) 是必须指定的,例如,如果列表包含 1 但不包含 4,则sum 是 5。然后当我们查看 1 时,我们想知道我们有多少 4,但是如果 4 不在 dict 中,那么它会引发错误。
  2. 再次遍历列表,计算每个整数出现的次数并将结果存储到字典中。
  3. 遍历dict,这次是找出我们有多少对。 我们需要考虑3个条件:

    3.1 key 只是 sum 的一半,并且这个 key 在 list 中出现不止一次,例如 list 是 [1,1,1],sum 是 2。我们把这个特殊情况当作代码所做的那样。

    3.2 key 只是 sum 的一半,并且这个 key 在列表中只出现一次,我们跳过这个条件。

    3.3 对于其他情况,键不是总和的一半,只需将其值与另一个键的值相乘,这两个键的总和为给定值。 例如,如果 sum 为 6,我们将 temp[1] 和 temp[5]、temp[2] 和 temp[4] 相乘,等等……(我没有列出数字为负数的情况,但想法是一样的。 )

最复杂的步骤是第 3 步,其中涉及搜索字典,但搜索字典通常很快,复杂度几乎不变。 (虽然最坏的情况是 O(n),但对于整数键不应该发生。)因此,假设搜索是常数复杂度,总复杂度是 O(n),因为我们只分别迭代列表多次。

欢迎提出更好的解决方案的建议:)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM