刷题使我快乐,满脸开心.jpg

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/combination-sum-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很相似,差别就在于本题
1、数组内元素会重复
2、每个数字在每个组合中只能使用一次
而39题中
1、无重复元素
2、每个元素可以多次使用

这个差距主要就体现在剪枝方法中了

  • 首先是这里不能重复使用同一元素,这个相对简单,只要保证搜索的起始位置持续往后推进即可
  • 其次一点,本次数组中会有重复元素,原本这里我是使用了map来记录使用情况,但是这个实现很复杂,因为要使用记录会根据深度产生变化。后来借鉴了精选答案中的一个方式,很是精妙:

对于同一层级来说,相同的元素得到的结果集一定是重复的,所以可以直接砍掉相同层级中后续相同元素的子树

代码

func combinationSum2(candidates []int, target int) [][]int {
	sort.Ints(candidates)
	return do(candidates, target, 0, len(candidates))
}

func do(candidates []int, target, start, end int) [][]int {
	res := make([][]int, 0)
	for k, num := range candidates {
		if k < start {
			continue
		}

		if num > target {
			break
		}
        // 砍掉相同层级,相等兄弟节点的子树
		if k > start && candidates[k] == candidates[k-1] {
			continue
		}

		if num == target {
			res = append(res, []int{num})
			break
		}

		resTemp := []int{num}
		resLoop := do(candidates, target-num, k+1, end)
		for _, resLoopItem := range resLoop {
			temp := append(resTemp, resLoopItem...)
			res = append(res, temp)
		}
	}
	return res
}