题目链接
https://leetcode.com/problems/combination-sum/
题目描述
给定一个无重复元素的数组candidates和目标值target,找出candidates中所有可以使数组和为target的组合。candidates中的数字可以无限制重复被选取。
包括target在内的所有数字都为正整数,解集不能包含重复的组合。1 <= candidates.length <= 30;1 <= candidates[i] <= 200;1 <= target <= 500。
示例
输入:candidates = [2,3,6,7],target = 7
输出:[[7],[2,2,3]]
解题思路一
参照宫水三叶的刷题日记,如何快速判断一个题是否用DFS+回溯来求解呢?
(1)题目求解的是所有方案(可行解),而不是方案数。由于求的是所有方案,就只能枚举,可能的解法有动态规划、记忆化搜索、DFS+回溯。
(2)看数据范围,DFS+回溯的数据范围通常会限制在30以内。动态规划或者记忆化搜索的数据范围可以到10^5~10^7
这道题中求的是满足数组和为target的组合,因此求解的是所有方案,同时candidates的数组长度控制在了30之内,因此此题可以用DFS+回溯来做。
参考【leetcode-Python】- 回溯(带模板)-46 Permutations中给出的回溯模板:
result = [] # 保存结果的全局变量
def backtrack(路径,选择列表):
if 满足结束条件:
result.append(路径)
return
for 选择 in 选择列表:
if 选择 不满足合法条件:
continue #剪枝
做选择
backtrack(新路径,新选择列表)
撤销选择
首先我们先把解空间树画出来,画树的过程就是在逐步求解的过程。每个节点存的状态为当前待凑的值,以及组合中已有的元素。只要元素的值小于等于当前待凑的值,该元素就可以作为选择。以target=7为根节点,当前可选择的元素个数为candidates中的元素个数。将选择看作解空间树种父节点和子节点之间的连线。最后搜索到待凑值为0时返回递归。
但是我们不难发现,上面的解中[2,2,3],[2,3,2],[3,2,2]是重复的组合。因此我们稍微改变一下搜索策略,在搜索时去重。
如果题目将相同元素按照不同顺序排列的组合视为重复,即不考虑顺序,那么在搜索时就要按照一定顺序来搜索,以避免出现重复解。在此题中,某节点的搜索范围设置为刚刚加入该节点路径的元素及该元素后面的元素。
那么解空间树改为:
因此在递归时要把子节点的搜索起点作为参数传入。
Python实现
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
def backtrack(begin,result,t,cur): #result为最终要返回的结果集,t表示待凑金额,cur为当前组合中的元素
if(t == 0):#递归结束条件,凑齐target
result.append(cur)
return
for i in range(begin,len(candidates)):#从begin开始搜索
if(candidates[i] > t):#如果当前的待凑金额小于元素值,这个元素值不可能出现在结果中,因此剪枝
continue
#由于要选择candidates[i],因此接下来要搜索的索引就要从i开始。
backtrack(i,result,t-candidates[i],cur+[candidates[i]])
result = []
cur = []
backtrack(0,result,target,cur)
return result