解法一:回溯
这道题的关键在于 candidates
中的元素不重复,但一个元素可以复用多次,概括就是元素无重可复选 , 之前子集/组合问题(参考这篇文章),为了避免重复选,我们每次循环 i
从 offset
开始遍历,循环中递归下一层回溯树是从 i + 1
即 offset + 1
开始遍历,从而保证 nums[offset]
这个元素不会被重复使用。
现在想让每个元素被重复使用,只要把 i + 1
改成 i
即可,在遍历这棵决策树的过程中,当前已经选择过的元素就可以在下一层递归被再次选择。
go
func combinationSum(candidates []int, target int) [][]int {
var res [][]int
var pathSum int
path := make([]int, 0)
backtrack(candidates, 0, target, path, &pathSum, &res)
return res
}
func backtrack(nums []int, offset int, target int, path []int, pathSum *int, res *[][]int) {
if *pathSum == target{ // 找到目标和
tmp := make([]int, len(path))
copy(tmp, path)
*res = append(*res, tmp)
return
}
if *pathSum > target{ // 超过目标和提前结束
return
}
for i:=offset; i<len(nums); i++{
// 做选择
path = append(path, nums[i])
*pathSum+=nums[i]
backtrack(nums, i, target, path, pathSum, res) // 递归起点仍在当前位置,当前元素可被下一层复选
// 撤销选择
path = path[:len(path)-1]
*pathSum-=nums[i]
}
}
扩展
来看这道题组合总和 II 题意变成元素有重不可复选 ,我们参考这篇文章扩展2中的解法,先对原数组排序,相同的元素放在相邻的位置上,然后在遍历决策树的过程中进行剪枝
go
func combinationSum2(candidates []int, target int) [][]int {
sort.Ints(candidates)
var res [][]int
var pathSum int
path := make([]int, 0)
backtrack(candidates, 0, target, path, pathSum, &res)
return res
}
func backtrack(nums []int, offset int, target int, path []int, pathSum int, res *[][]int){
if pathSum == target{
tmp := make([]int, len(path))
copy(tmp, path)
*res = append(*res, tmp)
return
}
if pathSum > target{ // 超过目标和,直接结束
return
}
for i:=offset; i<len(nums); i++{
if i > offset && nums[i] == nums[i-1]{ // 值相同的树枝,只遍历第一条
continue
}
path = append(path, nums[i])
pathSum+=nums[i]
backtrack(nums, i+1, target, path, pathSum, res)
path = path[:len(path)-1]
pathSum-=nums[i]
}
}