文章目录
题目描述
给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]示例 2:
输入: candidates = [2,5,2,1,2], target = 5,
输出:
[
[1,2,2],
[5]
]
思路分析
这道题是 39. 组合总和 的进化版,先做39再做这个比较好。
39题不需要去重,这道题升级了一下,需要去重了,虽然只加了两行代码,不过这里面很难理解。
老规矩,回溯三步走:
1.确定函数参数:
首先就是题目所给的target ,然后是我们计算的sum,走过的路径的节点也得记录一下用path,答案集合也得记录一下用res,开始下标值也得记录一下用startindex。
2.确定终止条件
这到题的终止条件和上一道题一样,
sum大于target时,直接return。
和sum等于target时,先将当前结果加入答案集,然后再return。
3.确定循环体:
首先是剪枝,当前的sum和即将递归的数值和如果大于目标值了,可以直接return掉。
然后就是最难的去重了,这里说一下利用startindex去重的方法,当然也有用数组记录去重的,那个比较容易理解就不说了。
首先要明白一点,就是startindex和for循环里的变量i分别代表着什么。
来个例子,假如初始数组[1,1,2],target=3,此时进入for i in (startindex,len(candidates ))
- 这时候的i和startindex都是0,单个分支下取第二个1之后的startindex 和 i 都是1。
- 再取2,此时的i和startindex都是2,sum已经等于4大于target了,那么开始回溯。
- 回溯到上一层,startindex和i都是1的时候,此时不取第二个1了,开始取2,此时的startindex为1,i为2。
拿文字描述比较乱,这块可以直接画个树就能明白了。
这块理解起来比较抽像,因为startindex是i的初始值,当横向遍历完之后,i会+1,而startindex没变,只有在单个分支纵向递归或者回溯的时候,startindex才加或者减。 可以片面的理解成,startindex控制纵向遍历的起始点,i控制横向遍历的下标。
这也是为什么这道题在回溯的过程中需要传i+1,而上一道题不需要+1的原因。(上一道题可以在单个组合中重复使用一个元素,这道题里不能。startindex控制纵向遍历起始点,一个组合中不能重复使用,所以往下传的时候+1,即只能使用本元素之后的元素。)
总结下来就是,在单个分支下的回溯 startindex始终<=i。 如果切换了分支 则i>startindex。
为什么要判断他是不是切换分支了?
其实是本题的要求,例子还是[1,1,2],你可以 ----第一个1和第二个1组成[1,1],因为他们使用了不同 1,只是值相同而已,这里显然是取第一个1下面的分支的操作过程,所以在一个组合里,元素是可以相同的,而如果你取的是第一个1和2,以及第二个1和2,这样就以为着有两个相同的组合 [1,2]了,虽然用的不是同一个1,但题目不允许这样。 这时候就是切分支带来的重复结果,所以我们要判断是不是切分支了,如果没切分支,重复是可以的,因为一个分支最终的叶子就是一个组合结果。
即 组合内可以有重复值,组合间不能有整体重复。
所以当 candidates[i] == candidates[i-1] 时候,可以判断重复了,i > startindex 可以判断切分支了,所以此时要进行去重了。
这块去重不能用return,毕竟后面还要继续判断呢,直接不处理他,continue就行了。
完整代码