心路历程:
这道题就差把回溯算法写在题面上了,其实这道题如果不是要遍历所有的可能情况,而是求某个最大最小值的话,就是一道经典的完全背包问题了。
这道题有一个注意的点,就是如何通过‘控制候选集合’来实现‘不重复子集’,第一反应是遍历到重复的不添加,但是这样做其实很费时间复杂度。最好的做法应该是用组合树去做,在每个状态下同时维护当前的候选集合,从而避免重复元素的选取。
回溯的这些排列/组合/子集问题,树形的区别其实就在于候选集合的维护上面,一个经典的方法就是用一个start_index记录当前结点可供选择的候选集合。这块labuladong的文章讲的很好。如果实在忘了,也可以按照全部遍历去掉重复的思路,就是很麻烦。
解法一:组合回溯
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
path = []
res = []
def dfs(current_target, start):
if current_target == 0:
res.append(path[:])
return
if current_target < 0:
return
for i in range(start, len(candidates), 1):
if current_target - candidates[i] >= 0:
path.append(candidates[i])
dfs(current_target - candidates[i], i) # 一个从根节点的分支把所有包含i的情况都搜索出来,因为i无限次拿取
path.pop()
dfs(target, 0)
return res
解法二:排列回溯+记录重复
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
path = []
res = []
visited = [] # 存储n元组,n的数量于candidates的大小一致
ntuple = [0] * len(candidates)
def dfs(current_target):
if current_target == 0:
if ntuple not in visited:
res.append(path[:])
visited.append(ntuple[:])
return
if current_target < 0:
return
for i, each in enumerate(candidates):
if current_target - each >= 0:
path.append(each)
ntuple[i] += 1
dfs(current_target - each)
ntuple[i] -= 1
path.pop()
dfs(target)
return res