40. 组合总和 II
题目
给定一个候选人编号的集合 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]
]
提示:
- 1 <= candidates.length <= 100
- 1 <= candidates[i] <= 50
- 1 <= target <= 30
解题思路
思路
这道题目和39.组合总和如下区别:
- 本题
candidates
中的每个数字在每个组合中只能使用一次。 - 本题数组candidates的元素是有重复的,而39.组合总和是无重复元素的数组
candidates
最后本题和39.组合总和要求一样,解集不能包含重复的组合。
本题的难点在于区别2中:集合(数组candidates
)有重复元素,但还不能有重复的组合
强调一下,树层去重的话,需要对数组排序!
要去重的是“同一树层上的使用过”,如果判断同一树层上元素(相同的元素)是否使用过了呢。
如果candidates[i] == candidates[i - 1] 并且 used[i - 1] == false,就说明:前一个树枝,使用了candidates[i - 1]
,也就是说同一树层使用过candidates[i - 1]
。
此时for循环里就应该做continue的操作。
在图中将used的变化用橘黄色标注上,可以看出在candidates[i] == candidates[i - 1]
相同的情况下:
used[i - 1] == true
,说明同一树枝candidates[i - 1]
使用过used[i - 1] == false
,说明同一树层candidates[i - 1]
使用过
实现
var (
res = make([][]int, 0) // 存放最终结果
path = make([]int, 0) // 存放临时结果
used = make([]bool, 0) // 存放是否使用了的标志
)
func backtracking(candidates []int, target int, sum int, startIndex int) {
if sum > target {
return
}
// 满足条件, 追加到最终结果中
if sum == target {
temp := make([]int, len(path))
copy(temp, path)
res = append(res, temp)
}
for i := startIndex; i < len(candidates) && sum+candidates[i] <= target; i++ { // 剪枝
// used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
// used[i - 1] == false,说明同一树层candidates[i - 1]使用过
// 要对同一树层使用过的元素进行跳过
if i > 0 && candidates[i] == candidates[i-1] && used[i-1] == false {
continue
}
sum += candidates[i]
path = append(path, candidates[i])
used[i] = true
backtracking(candidates, target, sum, i+1)
sum -= candidates[i]
path = path[:len(path)-1]
used[i] = false
}
}
func combinationSum2(candidates []int, target int) [][]int {
// !!!!!先要进行排序
sort.Ints(candidates)
res = make([][]int, 0)
path = make([]int, 0)
used = make([]bool, len(candidates))
backtracking(candidates, target, 0, 0)
return res
}