39. 组合总和
1. 题目:
给定一个无重复元素的数组 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
2. 题解1:回溯法(递归DFS)
主要思路:
- 每个递归结点,保存输入数组,目标数,结果集,当前和,当前开始下标,当前列表
- 避免出现重复组合,需要加入一个 start 下标索引,在每次进入下一层时,选择的数都是从上一层选择的数开始, 即保证该索引之前的数不再重复选择
代码:
使用差来进行状态保存
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> ans = new ArrayList<>();
dfs(target, new ArrayList<>(), 0, candidates, ans);
return ans;
}
public void dfs(int remain, List<Integer> list, int start, int[] candidates, List<List<Integer>> ans) {
if (remain < 0) return;
if (remain == 0) {
ans.add(new ArrayList<>(list));
return;
}
for (int i = start; i < candidates.length; i++) {
list.add(candidates[i]);
dfs(remain - candidates[i], list, i, candidates, ans);
list.remove(list.size() - 1);
}
}
}
写法2:
class Solution {
List<List<Integer>> res = new ArrayList<>();
int[] candidates;
int target;
public List<List<Integer>> combinationSum(int[] candidates, int target) {
this.candidates = candidates;
this.target = target;
dfs(0, 0,new ArrayList<>());
return res;
}
/**
* 递归函数
* @param sum 当前和
* @param start 当前开始下标
* @param list 当前列表
*/
public void dfs(int sum, int start, List<Integer> list) {
// 递归终止条件判断, 如果当前结点和已经大于target,结束搜索
if (sum > target) {
return;
}
if (sum == target) {
res.add(new ArrayList<>(list));
return;
}
for (int i = start; i < candidates.length; i++) {
list.add(candidates[i]);
int tempSum = sum + candidates[i];
dfs(tempSum, i, list);
// 消除状态影响
list.remove(list.size() - 1);
}
}
}
3. 题解2: 优化:排序+剪枝
主要思路:
- 先对数组进行排序,然后当 下一层结点的 sum>target 时,提前结束for循环
- 该方法代码只比题解1,多了三四行,基本沿用题解1的代码
- 速度上可以提升不少,实际测试时题解1用时8ms,此方法用时3ms
代码:
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> ans = new ArrayList<>();
Arrays.sort(candidates);
dfs(target, new ArrayList<>(), 0, candidates, ans);
return ans;
}
public void dfs(int remain, List<Integer> list, int start, int[] candidates, List<List<Integer>> ans) {
if (remain < 0) return;
if (remain == 0) {
ans.add(new ArrayList<>(list));
return;
}
for (int i = start; i < candidates.length; i++) {
//剪枝判断,由于数组有序,当这一个结点sum>target ,停止for循环
int tempRemain = remain - candidates[i];
if (tempRemain < 0) {
break;
}
list.add(candidates[i]);
dfs(tempRemain, list, i, candidates, ans);
list.remove(list.size() - 1);
}
}
}
**写法2: **
class Solution {
List<List<Integer>> res = new ArrayList<>();
int[] candidates;
int target;
public List<List<Integer>> combinationSum(int[] candidates, int target) {
this.candidates = candidates;
// 对数组进行排序
Arrays.sort(candidates);
this.target = target;
dfs(0, 0, new ArrayList<>());
return res;
}
/**
* 递归函数
*
* @param sum 当前和
* @param start 当前开始下标
* @param list 当前列表
*/
public void dfs(int sum, int start, List<Integer> list) {
// 递归终止条件判断, 如果当前结点和已经大于target,结束搜索
if (sum > target) {
return;
}
if (sum == target) {
res.add(new ArrayList<>(list));
return;
}
for (int i = start; i < candidates.length; i++) {
//剪枝判断,由于数组有序,当这一个结点sum>target ,停止for循环
int tempSum = sum + candidates[i];
if (tempSum > target) {
break;
}
list.add(candidates[i]);
dfs(tempSum, i, list);
// 消除状态影响
list.remove(list.size() - 1);
}
}
}
- 参考题解:
回溯算法 + 剪枝(回溯经典例题详解)