题目描述
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target 的不同组合数少于 150 个。
示例 1:
输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。
示例 2:
输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]
示例 3:
输入: candidates = [2], target = 1
输出: []
提示:
1 <= candidates.length <= 30
1 <= candidates[i] <= 200
candidate 中的每个元素都 互不相同
1 <= target <= 500
解析:
给定一个无重复元素数组,一个整数目标值,求数组元素可重复组合的和为目标值的情况。
这个题一上去无脑暴力把所有可能都搞出来也不是不可以,但是感觉会非常麻烦,因此作为一个懒人,当然是想着用一种更方便的方法。第一时间的解决思路就是递归,而所有元素可以重复的条件,代表着每一个元素都可能有不同的路径,所以为了拿到所有情况,需要进行回溯。
go语言做排序略微麻烦,我们假设已经做了排序,那么以实例一candidates [2,3,6,7]来看,我们可以把递归的点定位为目标值减去元素。
什么意思呢?就像上图所示的那样,我们用7减去数组的第一个元素,当余数大于零,说明我们找到了一个可能组合的一个数,需要找到可以组成余数的其他数,也就是通过递归让我们的目标值减小,当我们找到目标值减去一个元素余数为零时,这条路径的数就是一个符合要求的组合,
以图中示例;
7-2=5
我们就需要再次调用函数找到满足5的组合,因为元素可以重复,所以仍然可以使用2
5-2=3同理往下进行
3-2=1
1-2=-1
当目标值被减完未找到相等情况时,说明这个[2,2,2,2]组合不通,就需要回溯了
递归过程如下:
- 遍历数组中的每一个数字。
- 递归枚举每一个数字可以选多少次,递归过程中维护一个target变量。如果当前数字小于等于target(相减结果大于零),我们就将其加入我们的路径数组中,相应的target减去当前数字的值。也就是说,每选一个分支,就减去所选分支的值。
- 当target == 0时,表示该选择方案是合法的,记录该方案,将其加入res数组中。
这样处理后会有一个问题,方案重复了。为了避免搜索过程中的重复方案,我们要去定义一个搜索起点(index),已经考虑过的数,以后的搜索中就不能出现,让我们的每次搜索都从当前起点往后搜索(包含当前起点),直到搜索到数组末尾,这样就可以避免重复方案了。
代码如下:
func combinationSum(nums []int,target int) (num [][]int) {
array:=make([][]int,0)
if nums==nil || len(nums)==0{
return array
}
getSetOfSum(nums,&array,[]int{},target,0)
return array
}
func getSetOfSum(nums []int,array *[][]int, b []int,target int,index int) {
if target==0{
tmp := make([]int, len(b))
copy(tmp,b)
*array= append(*array,tmp)
return
}
for i:=index;i<len(nums);i++{
if target<0{
return
}
b=append(b,nums[i])
getSetOfSum(nums,array,b,target-nums[i],i)
b=b[:len(b)-1]
}
}
需要注意的是,go语言函数的参数传递是值拷贝,使用如果使用函数处理,则需要传递存储结果的二维数组的地址,在得到目标数组后添加时还要注意目标数组是动态维护的,因此需要拷贝赋值。
呐,如果使用熟练也可以采用函数类型变量内部递归,减少递归传递的参数,提高空间利用。
func CombinationSum(candidates []int, target int) [][]int {
var res [][]int
var path []int
sum := 0
var backTrack func(int)
backTrack = func(start int){
// 递归终止条件
// 若当前路径和等于目标和target,将此路径添加到结果集中
if sum == target{
temp := make([]int, len(path))
copy(temp, path)
res = append(res, temp)
return
}
// 剪枝优化,如果sum已经大于目标和target,就可以返回了,继续遍历没有意义
if sum > target{
return
}
for i:=start;i<len(candidates);i++{
sum += candidates[i]
path = append(path, candidates[i])
// 因为candidates中的元素可以无限制重复选取,所以这里不再是i+1了
// 递归
backTrack(i)
// 回溯
sum -= candidates[i]
path = path[:len(path)-1]
}
}
backTrack(0)
return res
}