LeetCode:40 组合总和2(Golang)
题目描述
给你一个整数数组 和一个目标数 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]]
分析
借鉴上一题的官方解答,把组合问题看作一棵树然后DFS遍历确实是个经典的思路(比我的砝码模型好使)。本题相较于上一题区别在于:
1.每个 candidate 只能用一次;
2.整数集合 candidates 包含重复数,但结果集内不能包含重复的组合;
针对第1条,每个candidate只能用一次:这其实会让我们的组合树变得更简单,树的高度(迭代次数)不会超过len(candidates);
针对第2条:整数集合 candidates 包含重复数,但结果集内不能包含重复的组合;
首先,我们需要先对 candidates 排序,让相同的 candidate 相邻,并且组合结果都是按从小到大排列,否则 [1,2,2] 和 [2,1,2] 是重复组合,由于二者内部排列顺序不同,我们不好去重,所以让它们都变成 [1,2,2]。
其次,我们可以完全 DFS 遍历这棵树,然后使用哈希表对结果进行去重。是一种办法,但本题提交会超时(因为 candidates 的边缘案例)。优化:假如 candidates 是 [1,2,2,5,…] ,观察遍历过程,索引为 1 的组合即:[1,2(索引1),…] )已经包含了索引为 2 的所有可能的组合(即:[1,2(索引2),…] )。所以优化的办法是,同一层级中,遇见相同的candidate,只沿第一个进行 DFS 遍历形成子树,其余重复candidate跳过,此称为大剪枝(即剪去重复candidate为根的子树)。
代码
func combinationSum2(candidates []int, target int) (res [][]int) {
sort.Ints(candidates)
remain := target
comb := []int{}
dfs := func(int) {}
dfs = func(idx int) {
if idx >= 0 {
comb = append(comb, candidates[idx])
remain -= candidates[idx]
//fmt.Println("comb:", comb) // test
}
if remain < 0 { // 小剪枝
return
}
if remain == 0 {
res = append(res, append([]int{}, comb...))
}
for i := idx + 1; i <= len(candidates)-1; i++ {
if i > idx+1 && candidates[i-1] == candidates[i] { // 大剪枝
continue
}
dfs(i)
remain += comb[len(comb)-1]
comb = comb[:len(comb)-1]
}
}
dfs(-1)
return
}