给定一个无重复元素的数组 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] ]
这是一道涉及回溯算法的题。可以转化为树结构的模型方便理解:
回溯就会涉及递归,可以参考递归四部曲:
递归作用:从candidates中的下标为startIndex的元素开始取数,一直取到终止条件
递归参数:candidates和target的意义如题意,startIndex表示这一层取数的起始点
终止条件:sum>target和sum==target
单层逻辑:这一层从startIndex开始取数,取一个数后接着下一层从startIndex开始取(因为同一数字可以重复选取,所以不用+1)
代码如下:
class Solution {
List<Integer> path = null;
List<List<Integer>> result = null;
int sum = 0;//记录每一层的和
void combinationSum1(int[] candidates, int target, int startIndex){
//递归作用:从candidates中的下标为startIndex的元素开始取数,一直取到终止条件
//参数:candidates和target的意义如题意,startIndex表示这一层取数的起始点
//终止条件:sum>target和sum==target
//单层逻辑:这一层从startIndex开始取数,取一个数后接着下一层从startIndex开始取(因为同一数字可以重复选取,所以不用+1)
if(sum > target) return;
if(sum == target){
result.add(new ArrayList(path));//如果直接result.add(path),那么add进result里的只是path的引用,由于回溯path的内容最终会被清空,那么最后path引用指向的就是空了,那么最后的结果就都是[].所以这里要new一个新的对象
return;
}
for(int i = startIndex; i < candidates.length; i++){//从startIndex开始取
sum += candidates[i];
path.add(candidates[i]);
combinationSum1(candidates, target, i);//如果从0开始取会有重复,比如[2,3],target=5.如果从这里不是startIndex,而是0,那么会有[2,3],[3,2]2个答案重复了。而又因为同一数字可以重复选取,所以不用+1。
sum -= candidates[i];
path.remove(path.size() - 1);//回溯,定义操作前的状态为初始状态,操作完后进行初始化
}
}
public List<List<Integer>> combinationSum(int[] candidates, int target) {
path = new ArrayList<>();
result = new ArrayList<>();
combinationSum1(candidates, target, 0);//从0开始取第一层的数
return result;
}
}
回溯算法最大的特点还是在操作完后要初始化这一层的状态(即代码中的path.remove(path.size() - 1);//回溯,定义操作前的状态为初始状态,操作完后进行初始化)。这里是将取数前的状态定义为初始状态了。