4

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

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

示例输入:[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我得到一个 size 的空列表列表2^(len(nums)),这是正确的大小,但数字不存在。temp但是,在我之前打印res.append(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)
4

2 回答 2

10

您将对同一列表对象的多个引用附加到res. 我们可以通过这样做来证明这一点

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

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

因此,您为temp“丢失”所做的各种更改,最终内容res将是 8 个对最终值的引用temp,在这种情况下,它是空列表。


temp解决此问题的简单方法是附加to的副本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包含一个空列表的列表开始。

在每个循环中,我们w通过循环创建一个新列表,z并将每个项目添加到w相应项目的副本中z,并将当前x附加到它。然后我们扩展z.w


只是为了好玩,这里有一个迭代生成器,它从位串生成子集(按自然顺序)。这种方法实际上非常有效,如果您想要一个大序列的所有子集而不消耗大量 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]
于 2017-08-14T19:11:09.987 回答
1

变量是对实际值的引用。但是,由于 Python 列表是可变的,因此当您通过一个引用更改值时,另一个引用也会反映这些更改。

>>> a = [1, 3]
>>> b = a
>>> b
[1, 3]
>>> b.append(1)
>>> b
[1, 3, 1]
>>> a
[1, 3, 1]

在将其附加到修复之前制作列表的副本。

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

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

如另一个答案所述,您也可以使用res.append(temp[:])

于 2017-08-14T19:07:29.703 回答