使用递归回溯生成集合的所有子集(Python)

问题描述

我试图理解回溯,但我陷入了这个问题,提示如下:

给定一组不同的整数,返回所有可能的子集。

示例输入:[1,2,3]

示例输出:[[], [1], [2], [3], [1, 2], [1, 3], [2, 3], [1, 2, 3]]

以下是我的代码:

def subsets(nums):
    res = []
    backtrack(res, [], nums, 0)
    return res

def backtrack(res, temp, nums, start):
    # print(temp)
    res.append(temp)
    for i in range(start, len(nums)):
        temp.append(nums[i])
        backtrack(res, temp, nums, i + 1)
        temp.pop() # Backtrack

当我返回res时,我得到一个大小为2^(len(nums))的空列表列表,它的大小是正确的,但数字不在那里。然而,在我执行之前打印temp显示Temp正在携带正确的输出。

例如

res = [[], [], [], [], [], [], [], []]

打印报表:

[] [1] [1, 2] [1, 2, 3] [1, 3] [2] [2, 3] [3]

为什么更改没有应用到res列表?

编辑1:

此解决方案有效,有什么区别?

def subsets(nums):
    res = []
    backtrack(res, [], nums, 0)
    return res

def backtrack(res, temp, nums, start):
    # print(temp)
    res.append(temp)
    for i in range(start, len(nums)):
        backtrack(res, temp + [nums[i]], nums, i + 1)

解决方案

您正在将对同一列表对象的多个引用追加到res。我们可以通过以下操作来演示这一点

result = subsets([1, 2, 3])
print([id(u) for u in result])

这将打印一个包含8个相同ID的列表。

因此,您对temp所做的各种更改将"丢失",res的最终内容将是对temp的最终值的8个引用,在本例中为空列表。


解决此问题的简单方法是将temp的副本追加到res

def subsets(nums):
    res = []
    backtrack(res, [], nums, 0)
    return res

def backtrack(res, temp, nums, start):
    res.append(temp[:])
    for i in range(start, len(nums)):
        temp.append(nums[i])
        backtrack(res, temp, nums, i + 1)
        temp.pop() # Backtrack

print(subsets([1, 2, 3]))

输出

[[], [1], [1, 2], [1, 2, 3], [1, 3], [2], [2, 3], [3]]

FWIW,我意识到这个练习的要点是练习递归,但在Python中最好避免递归,除非您真的需要它(例如,用于处理像树这样的递归数据结构)。但这里有一个更紧凑的迭代解决方案。

def subsets(seq):
    z = [[]]
    for x in seq:
        z += [y + [x] for y in z]
    return z

若要了解其工作原理,我们可以对其进行一点扩展,然后添加print调用。

def subsets(seq):
    z = [[]]
    for x in seq:
        print('z =', z, 'x =', x)
        w = []
        for y in z:
            w += [y + [x]]
        z += w
    return z

result = subsets([1, 2, 3])
print(result)  

输出

z = [[]] x = 1
z = [[], [1]] x = 2
z = [[], [1], [2], [1, 2]] x = 3
[[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]
我们从包含单个空列表的列表z开始。

在每个循环中,我们通过循环z并使w中的每个项都是z中的相应项的副本来创建一个新的列表w,并将当前的x附加到该列表中。然后,我们使用w的内容扩展z


只是为了好玩,这里有一个迭代生成器,它从位串(按自然顺序)生成子集。这种方法实际上非常有效,如果您希望在不消耗大量RAM的情况下获得一个大序列的所有子集,那么它是很好的。

def subsets(seq):
    w = len(seq)
    for i in range(1<<w):
        yield [u for u, v in zip(seq, reversed('{:0{}b}'.format(i, w))) if v=='1']

print(*subsets([1, 2, 3]))

输出

[] [1] [2] [1, 2] [3] [1, 3] [2, 3] [1, 2, 3]

相关文章