心路历程:
这道题就差把回溯算法写在题面上了,其实这道题如果不是要遍历所有的可能情况,而是求某个最大最小值的话,就是一道经典的完全背包问题了。
这道题有一个注意的点,就是如何通过‘控制候选集合’来实现‘不重复子集’,第一反应是遍历到重复的不添加,但是这样做其实很费时间复杂度。最好的做法应该是用组合树去做,在每个状态下同时维护当前的候选集合,从而避免重复元素的选取。
回溯的这些排列/组合/子集问题,树形的区别其实就在于候选集合的维护上面,一个经典的方法就是用一个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[:])returnif current_target < 0:returnfor 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[:])returnif current_target < 0:returnfor i, each in enumerate(candidates):if current_target - each >= 0:path.append(each)ntuple[i] += 1dfs(current_target - each)ntuple[i] -= 1path.pop()dfs(target)return res