每日力扣-组合总和
今天作为小菜菜的我花了好长时间在这类题型上,做了组合总和与组合总和Ⅱ,花的时间长不可怕,重要的是能领悟,能学到东西!!
这类题需要尝试将数组的元素进行各种组合以至于累加到目标数,很明显题意告诉我们要用递归回溯的思想,进行不断的尝试不断地累加不断地回溯,这样肯定有一定地效率损失,所以要做到一定地优化,我目前知道地优化貌似就是:剪枝,剪去不必要地递归,提升效率~说实话要不是做题我都不知道剪枝是个什么东西,所以实践多做题很重要,有利于扩阔视野,长见识。
接招:组合总和Ⅰ
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
示例 1:
输入:candidates = [2,3,6,7], target = 7,
所求解集为:
[ [7], [2,2,3]]
示例 2:
输入:candidates = [2,3,5], target = 8,
所求解集为:
[ [2,2,2,2],[2,3,3], [3,5]]
答:这道题中重要都字眼我已经标粗了,就是数组中的元素可以重复使用,比如例子中的 [2,3,6,7],可以通过下面的图很清晰的表示:
右边向下一个元素递归,而左边继续以本元素递归,然后根据条件退出递归回溯到上一级继续尝试不同的组合。以下我们看代码,各步解析写在注释中,为了方便看注释,把反斜杠反过来了~
func combinationSum(candidates []int, target int) [][]int {
sort.Ints(candidates) \\排序一下数组,方便剪枝操作
var res [][]int
var temp []int
var dfs func(int,int)
dfs = func(target,index int){ \\递归方法,根据目标值和数组下标递归
\\成功案例结果 刚好target = 0,所以该组合成立
if target == 0{
res = append(res,append([]int(nil),temp...)) \\不可以直接temp不然结果错乱是同一个切片
return
}
\\退出递归条件:到数组末尾
if index == len(candidates){
return
}
\\剪枝:如果数组中有重复数,重复的那个直接跳过不考虑,因为数组中的数可以重复使用,重复的数没有意义
if index > 0 && candidates[index] == candidates[index-1] {
return
}
\\开始递归:使用本身或者使用数组下一个数
if target - candidates[index] >= 0{ \\大于等于0说明可能可以组成一种结果
temp = append(temp,candidates[index]) \\将该结果保存到临时数组里
dfs(target-candidates[index],index) \\使用本数index递归,但target是他们的差,因为本数已经是temp的一个解
temp = temp[:len(temp)-1] \\剔除该数,恢复到原状,换另外一种情况尝试
}else{ \\剪枝:由于数组排序过,如果当前index的数大于了target,那数组后面的数也同样大于,无需再考虑!
return
}
\\本身数尝试完就测试数组下一个数
dfs(target,index+1)
}
\\从0下标开始递归
dfs(target,0)
return res
}
我觉得我注释解析写的挺清楚的,希望你看得懂~
再接招:组合总和Ⅱ
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的每个数字在每个组合中只能使用一次。
说明:
所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。
示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[ [1, 7],[1, 2, 5],[2, 6], [1, 1, 6]]
示例 2:
输入: candidates = [2,5,2,1,2], target = 5,
所求解集为:
[ [1,2,2],[5]]
答:这道题和组合总和Ⅰ一样利用递归回溯来实现,但是它的结果集中,数组的每个数只能用一次,所以面对数组中有多个重复数的情况,我们要考虑的是去重!否则只按Ⅰ的思路来结果会有重复值,因为比如2,5,2,1,2,target是5,1可以和其中三个2随机组成5,结果是5,并且利用数组元素不重复,但是偏偏结果集是重复的,所以首要任务去重,具体做法就是在Ⅰ的基础上,判断以当前下标为起点,如果刚刚用的数下一个数仍然是当前数,那就直接跳过。比如1刚刚和2组完队,for循环下一个的时候还是2,那就直接跳过~来看代码:
func combinationSum2(candidates []int, target int) [][]int {
sort.Ints(candidates)
var res [][]int
var temp []int
var dfs func(int,int)
dfs = func (target,index int){
if target == 0{
res = append(res,append([]int(nil),temp...))
return
}
\\前面代码没多啥变化
\\考虑的就是比如1可以和多个2组成一组,但不可以分别和多个2组成一组,因为属于重复
for i:=index;i<len(candidates);i++{ \\通过for循环来往下递归
\\剪枝:如果当前数和上一个数相同,上一个数是组过队了,所以没必要在组一次
if i-1 >= index && candidates[i] == candidates[i-1]{
continue
}
if target - candidates[i] >= 0{
temp = append(temp,candidates[i])
dfs(target-candidates[i],i+1)
temp = temp[:len(temp)-1]
}else{\\剪枝:对于排序过的数组,当前数大于target后面就不用比了
return
}
}
}
dfs(target,0)
return res
}
看到这里,今天的文就结束咯
感谢观看,要记得每天都学习喔~