这是我参与更文挑战的第25天,活动详情查看: 更文挑战
组合总和(题号39)
题目
给定一个无重复元素的数组 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]
]
提示:
1 <= candidates.length <= 30
1 <= candidates[i] <= 200
candidate
中的每个元素都是独一无二的。1 <= target <= 500
链接
解释
这题啊,这题是朴实无华。
看到这题的第一反应是用DP来做,可左思右想也想不出合适的DP思路。
为什么用不了DP呢?原因就在于这句话👇:
candidates
中的数字可以无限制重复被选取。
既然都可以无限制被重复选取了,就证明DP是用不了的。
而且题目还有一点小坑,这一点笔者倒是很早就发现了,看官方给的两个示例:
[2,3,6,7]
[2,3,5]
这两个示例都是升序的,再加上题目说的数字是不重复的,很容易让人联想到这是一个没有重复数字的升序数组,结果其实并不是的,升序是不存在的,这里的用例就是凑巧是升序的而已。
不用DP这题应该怎么解呢?很简单——回溯。
用了回溯就很简单了,这里只需要进行两部回溯操作。
在开始介绍解法之前,先想想回溯到定义,笔者认为回溯就是不断尝试所有可能性,遇到合适的结果就记住,直到尝试完所有的可能性位置。
放到这题里,需要尝试的地方只有一个,换成题目里的一句话就是:
candidates
中的数字可以无限制重复被选取。
只要在每次循环的时候再从当前元素开始就好了,当然了,这里有个条件,就是目标值要大于等于当前元素的值,否则明显就超过了,这还尝试啥。
而且如果没有这个条件,回溯会无限进行下去。
在进行回溯的过程中需要记住三个东西:
- 当前的数组
- 目标和或者剩余的目标值
- 当前的位置(index)
每次回溯会有两种情况:
-
不进行数字选择
数组和剩余目标值都不用动 ,直接
index + 1
即可。 -
进行数字选择
选了数字,目标值就得减去当前数字了,同时数组需要增加一位,
index
不用动。
说到这里,回溯的写法也就呼之欲出了,推荐自己试试再看下面的答案。
自己的答案
无
更好的方法(回溯)
话不多说,先看代码👇:
var combinationSum = function(candidates, target) {
var res = []
function DFS(arr, index, target) {
if (index === candidates.length) return
if (target === 0) return res.push(arr)
// 不选当前元素,直接下一个元素
DFS(arr, index + 1, target)
// 如果条件合适,可以再次选择当前元素
if (target - candidates[index] >= 0) {
DFS([...arr, candidates[index]], index, target - candidates[index])
}
}
DFS([], 0, target)
return res
};
大部分代码都和解释中说的一样,这里可以注意下这行代码👇:
if (index === candidates.length) return
这行代码的意思是如果当前index
已经走到头了,超过了数组的长度,那就终止此次回溯,这样写其实也可以👇:
if (!candidates[index]) return
都是一样的道理。
其它的就和解释中说的一样了,官方说可能还有更好的剪枝方法,但笔者并没有想到,翻阅了一些别的答案,好像也没有更好的剪枝方案,如果有同学了解欢迎在评论区留言。
PS:想查看往期文章和题目可以点击下面的链接:
这里是按照日期分类的👇
经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇
有兴趣的也可以看看我的个人主页👇