首先,我将添加一些换行符以使其更易于阅读:
def subsets_with_sum(lst, target, with_replacement=False):
x = 0 if with_replacement else 1
def _a(idx, l, r, t):
if t == sum(l):
r.append(l)
elif t < sum(l):
return
for u in range(idx, len(lst)):
_a(u + x, l + [lst[u]], r, t)
return r
return _a(0, [], [], target)
您会注意到的第一件事是subsets_with_sum 定义了一个函数并在最后一个return 语句中调用它一次。第一个元素是您从中采样值的列表,第二个是目标总和,第三个是告诉函数是否可以多次使用 lst 中的单个元素的参数。
因为_a 是在subsets_with_sum 中定义的,所以它封闭 将值传递给subsets_with_sum - 这很重要,lst 对于@ 的每个递归调用都是相同的987654329@。要注意的第二件事是l 是一个值列表,而r 是一个列表列表。重要的是,l 是每个_a 调用的不同对象(这是由于l + [lst[u]] 导致l 的副本与[lst[u]] 连接)。但是,r 表示每个_a 调用的相同 对象-r.append(l) 会对其进行修改。
我将从with_replacement == True 开始,因为我觉得它更简单。第一次调用_a 传递0, [], [], target。然后它检查是否t == sum(l) 并将其附加到r,因此r 的唯一元素将是总和为t(或target)的列表。如果sum(l) 返回None,则该递归分支被丢弃。如果l 的元素总和小于target,那么对于lst 中的每个 元素,该元素将附加到l 的副本并使用该列表调用 _a。这是打印出步骤的函数的修正版本,因此我们可以检查发生了什么:
def subsets_with_sum(lst, target, with_replacement=False):
x = 0 if with_replacement else 1
def _a(idx, l, r, t, c):
if t == sum(l):
r.append(l)
elif t < sum(l):
return
for u in range(idx, len(lst)):
print("{}. l = {} and l + [lst[u]] = {}".format(c, l, l + [lst[u]]))
_a(u + x, l + [lst[u]], r, t, c+1)
return r
return _a(0, [], [], target, 0)
调用subsets_with_sum([1,2,3], 2, True) 会打印以下内容(我重新排序并稍微分开打印的行,以便于阅读):
0. l = [] and l + [lst[u]] = [1]
0. l = [] and l + [lst[u]] = [2]
0. l = [] and l + [lst[u]] = [3]
1. l = [1] and l + [lst[u]] = [1, 1]
1. l = [2] and l + [lst[u]] = [2, 2]
1. l = [2] and l + [lst[u]] = [2, 3]
1. l = [1] and l + [lst[u]] = [1, 2]
1. l = [1] and l + [lst[u]] = [1, 3]
2. l = [1, 1] and l + [lst[u]] = [1, 1, 1]
2. l = [1, 1] and l + [lst[u]] = [1, 1, 2]
2. l = [1, 1] and l + [lst[u]] = [1, 1, 3]
您可以看到c 级别的右列 (l + [lst[u]]) 等于 c + 1 级别的左列 (l)。此外,请注意[3] - 最后一行c == 0 没有超过elif t < sum(l),因此它不会被打印。类似地,c == 1 处的 [2] 被附加到 r,但仍然分支继续到下一个级别,因为 lst 的一个或多个元素可能等于 0,因此将它们附加到 [2] 可能结果是另一个列表,总和为target。
另外,请注意对于特定级别c 的每个列表,其最后一个元素是lst[u] 将出现len(lst) - u-次在下一个级别c + 1,因为这是列表的组合数每个lst[z],其中z >= u。
那么with_replacement == False(默认情况)会发生什么?好吧,在这种情况下,每次将lst[u] 添加到l 和sum(l) < t,以便l 的特定实例继续到下一个递归级别,只有lst[z] 可以添加到列表中,其中@987654383 @,因为 u + 1 被传递给各自的 _a 调用。让我们看看当我们调用subsets_with_sum([1,2,3], 2, False)时会发生什么:
0. l = [] and l + [lst[u]] = [1]
0. l = [] and l + [lst[u]] = [3]
0. l = [] and l + [lst[u]] = [2]
1. l = [1] and l + [lst[u]] = [1, 1]
1. l = [1] and l + [lst[u]] = [1, 2]
1. l = [1] and l + [lst[u]] = [1, 3]
1. l = [2] and l + [lst[u]] = [2, 2]
1. l = [2] and l + [lst[u]] = [2, 3]
2. l = [1, 1] and l + [lst[u]] = [1, 1, 1]
2. l = [1, 1] and l + [lst[u]] = [1, 1, 2]
2. l = [1, 1] and l + [lst[u]] = [1, 1, 3]
现在观察对于特定级别c 的每个列表,其最后一个元素是lst[u] 将出现len(lst) - u - 1-次在下一个级别c + 1,因为这是每个列表的组合数lst[z],其中z > u。将此与上面分析的with_replacement=True 进行比较。
我建议您多尝试一下,直到您更好地理解它为止。不管替换如何,重要的是要意识到 return 语句总是返回给调用者。 _a 的第一次调用返回到 subsets_with_sum,然后返回到调用者(可能是您或其他一些函数)。对_a 的所有其他(递归)调用返回到_a 调用的先前实例,这些调用只是丢弃返回的值(或者更准确地说,不费心对它做任何事情)。你得到正确的r 的原因是因为它的行为有点像一个全局值 - 每个找到解决方案的_a 调用都会将它添加到同一个r。
最后,subsets_with_sum 的工作方式与我期望的基于其所需功能的完全不同。尝试以下两个调用:subsets_with_sum([2,2,1], 5, True) 和 subsets_with_sum([2,2,1,1,1], 5, False)。