我有一个数字列表。我也有一定的金额。总和来自我的列表中的一些数字(我可能/可能不知道它的数量是多少)。是否有快速算法来获取可能的数字列表?用Python编写会很棒,但伪代码也很好。 (除了Python,我还读不出任何东西:P)
实施例
list = [1,2,3,10]
sum = 12
result = [2,10]
注意:我知道Algorithm to find which numbers from a list of size n sum to another number(但我无法阅读C#,我无法检查它是否适合我的需求。我在Linux上,我尝试使用单声道,但我得到错误,我无法弄清楚如何工作C#:(
AND 我知道algorithm to sum up a list of numbers for all combinations(但似乎效率很低。我不需要所有组合。)
答案 0 :(得分:36)
此问题会缩减到0-1 Knapsack Problem,您要在其中找到具有精确总和的集合。解决方案取决于约束条件,在一般情况下,此问题是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.这是因为它假设前十个与后十个不同。可以修复算法以假设两个十位相等(从而回答一个),但这有点复杂。
答案 1 :(得分:4)
我知道自您提出这个问题以来,十年后我就给出了答案,但是我真的很想知道如何用jbernadas的方法对我来说太难了,所以我用Google搜索了一个小时,找到了可以完成工作的python库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)
希望有帮助!
答案 2 :(得分:2)
因此,逻辑是对数字进行反向排序,并假设数字列表为 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 。
有两种可能的情况 i 是和的一部分。
因此,我们假设 i 是解决方案的一部分,然后问题减少为 l 为l[l.index(i+1):]
而 s 为 si 所以,如果我们的函数是(l,s),那么我们调用a(l[l.index(i+1):] ,s-i)
。如果我不是 s 的一部分,那么我们必须从l[l.index(i+1):]
列表中形成 s 。
因此在两种情况下都是类似的,只有改变是如果我是s的一部分,则s = s-i,否则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..but 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毫秒,之前的代码也使用了负数。
所以我可以做一些改进。
答案 3 :(得分:0)
Rx.Observable.prototype.finally(action)
这个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
答案 4 :(得分:0)
我找到了一个答案,其中运行时复杂度为O(n),空间复杂度约为O(2n),其中n是列表的长度。
答案满足以下限制条件:
列表可以包含重复项,例如[1,1,1,2,3]你想找到对的总和为
列表可以包含正整数和负整数
代码如下,然后是解释:
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)
通过dict迭代,这次是找到我们有多少对。我们需要考虑3个条件:
3.1密钥只是总和的一半,并且该密钥在列表中出现不止一次,例如list是[1,1,1],sum是2.我们将这个特殊条件视为代码所做的。
3.2密钥只是总和的一半,这个密钥只在列表中出现一次,我们跳过这个条件。
3.3对于其他情况,密钥不是总和的一半,只需将其值乘以另一个密钥的值,其中这两个密钥总和为给定值。例如。如果sum是6,我们乘以temp [1]和temp [5],temp [2]和temp [4]等...(我没有列出数字为负数的情况,但想法是相同的。)
最复杂的步骤是步骤3,其中涉及搜索字典,但是搜索字典通常是快速的,几乎恒定的复杂性。 (虽然最坏的情况是O(n),但不应该发生整数键。)因此,假设搜索是恒定的复杂性,总复杂度是O(n),因为我们只分别多次迭代列表。
欢迎提供更好的解决方案:)