import java.util.Arrays;
import java.util.List;
public class ConbinnationSum {
List<List<Integer>> ll=new ArrayList<List<Integer>>();
public static void main(String[] args) {
int[] c={10,1,2,7,6,1,5};
new ConbinnationSum().combinationSum(c, 8);
// ArrayList<Integer> l=new ArrayList<Integer>();
// new ConbinnationSum().testNode(3,l,0,7,c);
}
public List<List<Integer>> combinationSum(int[] candidates, int target) {
int sum=0;
ArrayList<Integer> l=new ArrayList<Integer>();
// List<List<Integer>> ll=new ArrayList<List<Integer>>();
Arrays.sort(candidates);
for(int i=0;i<candidates.length && candidates[i]<=target;i++){
testNode(i,l,sum,target,candidates);
}
System.out.print(ll.toString());
return ll;
}
void testNode(int i, ArrayList<Integer> l, int sum, int target,int[] candidates) {
// TODO Auto-generated method stub
if(sum==target){
if(!ll.contains(new ArrayList<Integer>(l))){
ll.add(new ArrayList<Integer>(l));
//这里注意 一定要用l新建一个List对象 而不能直接ll.add(l);
// 因为List<List<Integer>> 中存的是 List<Integer>对象的存储地址 直接ll.add(l) 这样ll 中存的全部是l这一个地址,
// 比如添加5次l,虽然添加的时候l中存的内容不一样 但最后ll中存的都是5个相同的值,这个值是最终l的内容
}
// l.remove(l.size()-1);
}
// sum+=candidates[i];
if(sum>target||i>candidates.length){
// sum=sum-candidates[i];
// l.remove(new Integer(candidates[i]));
}else{
l.add(candidates[i]);
sum+=candidates[i];
int n=l.size();
int j=i+1;
do{
testNode(j,l,sum,target,candidates);
j++;
}while(j<candidates.length&&candidates[j]<=target);
// for(int j=i+1;j<candidates.length&&candidates[j]<=target;j++){//注意 for循环 对i的处置也要进行条件判断 不符合条件 直接跳过循环内容
// testNode(j,l,sum,target,candidates);
// }
l.remove(new Integer(candidates[i]));
sum=sum-candidates[i];
}
}
}
public List<List<Integer>> combinationSum(int[] nums, int target) {
List<List<Integer>> list = new ArrayList<>();
Arrays.sort(nums);
backtrack(list, new ArrayList<>(), nums, target, 0);
return list;
}
private void backtrack(List<List<Integer>> list, List<Integer> tempList, int [] nums, int remain, int start){
if(remain < 0) return;
else if(remain == 0) list.add(new ArrayList<>(tempList));
else{
for(int i = start; i < nums.length; i++){
tempList.add(nums[i]);
backtrack(list, tempList, nums, remain - nums[i], i); // not i + 1 because we can reuse same elements
tempList.remove(tempList.size() - 1);
}
}
}
回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就剪枝“回溯”返回,尝试别的路径。首先最重要的是针对所给问题,确定问题的树形(图形)解空间,然后以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。一般用递归的方法实现回溯算法:给出一个回溯递归函数的框架,
递归函数的函数功能可理解为 在当前情况下负责搜索 t节点为根的子树 所需要进行的操作。如上面Conbination Sum问题中 的递归函数testNode(int i, ArrayList<Integer> l, int sum, int target,int[] candidates)就是 在 之前累积的和 为sum 临时选择的整数子集为l 时遇到 节点i 时所需做的操作:判断是不是之前的情况已满足要求(及sum是否等于target),满足就输出 子集l ,判断sum是否大于target 大于的话就将通向i的这支减掉,小于的话说明 可以将 i 加入l 并搜索i下面的子树 (for循环) 记得在搜索完其子树后要 恢复 l 和 sum(l.remove(new Integer(candidates[i])); sum=sum-candidates[i];)。
而
backtrack(List<List<Integer>> list, List<Integer> tempList, int [] nums, int remain, int start)
这个递归函数 处理的情况是 面对 start节点已经加入子集 后的搜索。
回溯法对解空间作深度优先搜索,因此,在一般情况下用递归方法实现回溯法。
// 针对N叉树的递归回溯方法
void backtrack (int t)
{
if (t > n) {
// 到达叶子结点,将结果输出
output (x);
}
else {
// 遍历结点t的所有子结点
for (int i = f(n,t); i <= g(n,t); i ++ ) {
x[t] = h[i];
// 如果不满足剪枝条件,则继续遍历
if (constraint (t) && bound (t))
backtrack (t + 1);
}
}
}