回溯法的应用
- 组合总和 给定一个无重复元素的数组 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] ]
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> res = new LinkedList<>();
Arrays.sort(candidates);//先排序,生成的结果自然有序
dfs(candidates, target, 0, 0, res, new LinkedList<>());
return res;
}
void dfs(int[] nums, int target, int index, int sum, List<List<Integer>> res, List<Integer> list){
//设计一个函数,就要确定它的变量,index, sum控制进度, res放结果,list放每次的结果,target作对比
if(sum == target){//边界条件
res.add(new LinkedList<>(list));
return;
}
if(sum > target) return;//及时剪枝,没有这个,会爆栈,因为不一定正好等于target
//每个元素可以重复多次,也可以不选,招商那题是每个元素都会选取,但是符号却又很多种可能,所以是对符号的可能情况进行了讨论
//同理对应的边界条件也变化,不再是index == nums.length;
//开头有很多种可能性,利用for循环
int len = nums.length;
if(index >= len) return;//其实这句不加也行,一般不会越界
for(int i = index; i < len; i++){//同时也控制了数组不会越界,与字符的全排列类似
list.add(nums[i]);//注意这里都是i,不是index
dfs(nums, target, i, sum + nums[i], res, list);//可以重复,所以仍以index = i;
//注意sum是在函数里面变化的,如果是在外面,需要加一层回溯
list.remove(list.size() - 1);
}
//这题同时要与全排列比较,全排列对应的for循环是从0-len,同时加了一个状态变量,保证每一个元素都被使用,同时边界条件为index== len, 而这里只需要和为给定的值即可
}
}
2.对上述代码进行优化,改变剪枝的位置
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> res = new LinkedList<>();
Arrays.sort(candidates);//先排序,生成的结果自然有序
dfs(candidates, target, 0, 0, res, new LinkedList<>());
return res;
}
void dfs(int[] nums, int target, int index, int sum, List<List<Integer>> res, List<Integer> list){
if(sum == target){//边界条件
res.add(new LinkedList<>(list));
return;
}
//if(sum > target) return;//及时剪枝
int len = nums.length;
for(int i = index; i < len; i++){
if(sum + nums[i] > target) break;//此时效率会得到极大提高
list.add(nums[i]);
dfs(nums, target, i, sum + nums[i], res, list);
list.remove(list.size() - 1);
}
}
}
3.换用减法,同样的思路
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> res = new LinkedList<>();
Arrays.sort(candidates);
dfs(candidates, target, 0, target, res, new LinkedList<>());
return res;
}
void dfs(int[] nums, int target, int index, int sum, List<List<Integer>> res, List<Integer> list){
//此时sum表示剩余的和
if(sum == 0){//边界条件
res.add(new LinkedList<>(list));
return;
}
int len = nums.length;
for(int i = index; i < len; i++){
if(nums[i] > sum) break;
list.add(nums[i]);
dfs(nums, target, i, sum - nums[i], res, list);
list.remove(list.size() - 1);
}
}
}
其实可以少一个参数
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> ans = new ArrayList<>();
Arrays.sort(candidates);
dfs(candidates, 0, target, ans, new ArrayList<Integer>());
return ans;
}
void dfs(int[] candidate, int index, int target, List<List<Integer>> ans, ArrayList<Integer> list){
if(0 == target){
ans.add(new ArrayList<>(list));
return;
}
if(0 > target) return;
int len = candidate.length;
if(index > len) return;
for(int i = index; i < len; i++){
if( candidate[i] > target) break;
list.add(candidate[i]);
dfs(candidate, i, target - candidate[i], ans, list);
list.remove(list.size() - 1);
}
}
}
L40
1.数组元素可重复
2.结果中每个数字只能使用一次
- 组合总和 II 给定一个数组 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] ]
注意每个数字智能使用一次,回溯的时候需要注意是i+1,不是i
1.做减法
class Solution {
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
List<List<Integer>> res = new LinkedList<>();
Arrays.sort(candidates);
dfs(candidates, target, 0, target, res, new LinkedList<>());
return res;
}
void dfs(int[] nums, int target, int index, int sum, List<List<Integer>> res, List<Integer> list){
//此时sum表示剩余的和
if(sum == 0){//边界条件
res.add(new LinkedList<>(list));
return;
}
int len = nums.length;
for(int i = index; i < len; i++){
if(nums[i] > sum) break;
//if(i > index && nums[i] == nums[i - 1]) break;//去重,注意不是跳出
//注意这里与全排列的区别,那个是i > 0,同时还有状态的判断
if(i >index && nums[i] == nums[i - 1]) continue;
list.add(nums[i]);
dfs(nums, target, i + 1, sum - nums[i], res, list);//第二个区别,只能使用一次
list.remove(list.size() - 1);
}
}
}
2.加法
class Solution {
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
List<List<Integer>> res = new LinkedList<>();
Arrays.sort(candidates);//先排序,生成的结果自然有序
dfs(candidates, target, 0, 0, res, new LinkedList<>());
return res;
}
void dfs(int[] nums, int target, int index, int sum, List<List<Integer>> res, List<Integer> list){
if(sum == target){//边界条件
res.add(new LinkedList<>(list));
return;
}
//if(sum > target) return;//及时剪枝
int len = nums.length;
for(int i = index; i < len; i++){
if(sum + nums[i] > target) break;//此时效率会得到极大提高
if(i > index && nums[i] == nums[i - 1]) continue;
list.add(nums[i]);
dfs(nums, target, i+1, sum + nums[i], res, list);
list.remove(list.size() - 1);
}
}
}