地址:https://leetcode-cn.com/problems/combination-sum/
题目描述:**
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取,也就是说选取过了,还可以继续选取。
示例:
输入:candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]
输入:candidates = [2,3,5], target = 8,
所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]
利用回溯的思路,是一种笨办法,遍历所有情况找出问题的解。
回溯的思路
用回溯算法解决问题的一般步骤:
- 针对所给问题,定义问题的解空间,它至少包含问题的一个(最优)解。
- 确定易于搜索的解空间结构,使得能用回溯法方便地搜索整个解空间 。
- 以深度优先的方式搜索解空间,并且在搜索过程中用剪枝函数避免无效搜索。
解决一个回溯问题,实际上就是一个决策树的遍历过程:
- 路径:已经做出的选择
- 选择列表:当前可以做的选择
- 结束条件:已到决策树底层,无法再做选择了
搜索思路:
先画一个遍历的递归树:
利用递归的方式,每一次递归调用函数,都会是树的一层元素,不断的选择路径,直到不满足条件,递归函数结束,再回溯到上一层树,继续选择路径。
题中要求可以同一个数字重复选取无限次,对同一个数字也要递归选取上。假如每次从数组开始循环时,会出现重复的结果集,这不符合题意,所以可以优化为 从上一层开始的位置向后选择
优化后的递归树:
可以看出下一层开始位置,是上一层元素的位置,这样有效避免又选了之前的元素,造成结果集重复。
代码实现思路:
选择的路径
一般解题思路是,定义一个临时的数组,存储选择过的列表。循环开始时,把该路径的元素存入数组,递归结束,回溯回来,从列表中弹出,继续进行下一轮的选择。
每次选择,要判断选择的路径中元素之和正好和 target 相同,则选择的路径符合要求,存入到结果集中。
结束条件
本题的结束条件为选择的路径的元素之和,大于了 target,则说明不符合条件,本次选择路径结束,停止递归。
示例代码:
/**
* @param {number[]} candidates
* @param {number} target
* @return {number[][]}
*/
var combinationSum = function(candidates, target) {
const len = candidates.length;
const child = []
const result = []
const loop = (index, sum) => { // index 从哪里开始 sum 上次计算总和
// 和目标相同,则找到了一对组合
if(sum === target) {
result.push(child.slice())
}
// 组合相加结果大于目标了,则说明不符合
if(sum > target) {
return
}
// i 每次从 0 开始,则有重复的结果集
// i 从上一层的位置开始,则有效避免重复集
for(let i = index; i < len; i++){
child.push(candidates[i])
loop(i, sum + candidates[i] )
child.pop()
}
}
loop(0,0)
return result
};
console.log(combinationSum([2,3,6,7], 7)) // [ [ 2, 2, 3 ], [ 7 ] ]
console.log(combinationSum([2,3,5], 8)) // [ [ 2, 2, 2, 2 ], [ 2, 3, 3 ], [ 3, 5 ] ]
总结:
本题和其他回溯的题目思路一致,只是在结束条件时稍微不同。通常结束条件是以到递归树的底层结束,本题是在大于 target 结束。
其他思路,待补充。。。。