内有部分力扣题解思想,侵删,仅作个人笔记使用。
参考:力扣题解
今天在写Leetcode78.子集一题时,一开始写了如下题解:
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
res = []
track = []
def dfs(res,track,nums,start):
res.append(track)
for i in range(start,len(nums)):
track.append(nums[i])
dfs(res,track,nums,i+1)
track.pop()
dfs(res,track,nums,0)
return res
最终输出的是:
[[], [], [], [], [], [], [], []]
后来看了一下力扣里面其他的题解,发现了这个问题,由于Python中的列表是可变的,如果这里直接传入track列表,回溯DFS函数里面在遍历二叉树后会撤销路径track.pop(),很明显最终回溯结束的时候,track列表里面是不含任何元素的,track改变为了空列表,随之res里面添加的列表也会一一变为空列表。
解决方案:
将res.append(track)改为res.append(track[:]),即在传入track列表时浅复制,传入的并不是track列表的指针。
代码如下:
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
res = []
track = []
def dfs(res,track,nums,start):
res.append(track[:])
for i in range(start,len(nums)):
track.append(nums[i])
dfs(res,track,nums,i+1)
track.pop()
dfs(res,track,nums,0)
return res
另一种解决方法:
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
res = []
n = len(nums)
def dfs(start, path):
res.append(path)
for i in range(start, n):
# python的函数传参属于值传递,即实参各个引用的副本,即别名
# 每一层的递归中,由于传递的参数是path + [nums[i]],那么path内存地址会发生变化
# 所以每一层的递归函数中,path的内存地址都不同
# 所以退回到上一层dfs函数中后,path变量也变为该层dfs函数中的path的内存,所以无需pop即实现了[1,2,3]->[1,2]
dfs(i + 1, path + [nums[i]])
dfs(0, [])
return res
这样的话每一次传入的path其实都是不同的列表(内存地址不相同),也就不需要维护前面的track列表。
Python列表引用与浅复制的区别:
a = [1, 2, 3]
b = a# 别名
c = a[:]# 浅复制
d = list()
d.append(a)# 别名
e = list(a)# 浅复制
a.append(4)
print(b)# [1, 2, 3, 4]
print(c)# [1, 2, 3]
print(d)# [[1, 2, 3, 4]]
print(e)# [1, 2, 3]
a = [1, 2, 3]
b = a# 别名
c = a[:]# 浅复制
d = list()
d.append(a)# 别名
e = list(a)# 浅复制
# 浅复制之后,变量名a指向新的地址,后续append就不会影响b和d了
# b和d扔指向原来的内存区域,且这块内存的引用值会减1,因为a不再指向它了
a = a[:]
a.append(4)
print(b)# [1, 2, 3]
print(c)# [1, 2, 3]
print(d)# [[1, 2, 3]]
print(e)# [1, 2, 3]