这个问题减少到
0-1 Knapsack Problem,在那里你试图找到一个精确的总和.解决方案取决于约束,在一般情况下,这个问题是NP-Complete.
但是,如果最大搜索总和(我们称之为S)不是太高,那么可以使用动态编程来解决问题.我将使用递归函数和memoization来解释它,这比自下而上的方法更容易理解.
我们来编写一个函数f(v,i,S),使得它返回v [i:]中与S精确匹配的子集数.为了递归地解决它,首先我们必须分析基数(即:v [我:]是空的)
> 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.这是因为它假设前十个与第二个十不同.该算法可以被固定为假定两者都相等(因此回答一个),但是这更复杂一些.