回溯的两种思路
看不同的解题方法,形成不同的思维。
先说结论。回溯解题思路1:是对可选择每个元素,采取不选择、选择两种策略,不断递归下去。最近看花花酱的视频,得到思路2:要完成目标分为n个阶段,每个阶段会有不同的选择,在每个阶段尝试不同选择。
下面,以具体leetcode39为例,来说明。
题目描述
输入:一个不包含重复元素的数组candidates,一个int target
输出:用candidates中的元素组成target,输出有多少种组合。输出结果不能重复。
规则:candidates中的元素可以使用多次。
例如candidates=[2,3,6,7] target=7,返回
[
[7],
[2, 2, 3]
]
按照思路1解决
结果集是包含多种解法,用List<List> result 表示。每一种解法是一个List list。
对于候选数组candidates中的每个元素,我们可能使用,也可能不使用。
例如对于index = 0,我们可能使用candidates[0],也可能不使用。
不使用candidates[0],target不变,list不变,index+1,进入选择candidates[1]阶段。
使用candidates[0],target变小,list添加使用candidates[0],index+1,进入选择candidates[1]阶段。注意因为题目说一个元素可以重复使用,那candidates[0]最多可以使用target/candidates[0]次。
在代码中使用path记录每个元素选择多少次。path[0]=candidates[0]选择的次数。
退出条件:当target=0,问题解决,添加结果集。下标超出范围的时候,或者target=0,退出循环。
private List<List<Integer>> result = new ArrayList<List<Integer>>();
private int[] path;
private int[] candidates;
/**
* path[i] = 使用nums[i]元素的次数
* @param candidates
* @param target
* @return
*/
public List<List<Integer>> combinationSumV2(int[] candidates, int target) {
result.clear();
path = new int[candidates.length];
this.candidates = candidates;
dfsV2(0, target);
return result;
}
private void dfsV2(int idx, int target) {
if (target == 0) {
List<Integer> solution = new ArrayList<Integer>();
for (int i = 0; i < path.length; i++) {
for (int j = 1; j <= path[i]; j++) {
solution.add(candidates[i]);
}
}
result.add(solution);
return;
}
if (idx >= candidates.length || target < 0) {
return;
}
int maxCount = target / candidates[idx];
for (int i = 0; i <= maxCount; i++) {
path[idx] = i;
dfsV2( idx + 1, target - i * candidates[idx]);
path[idx] = 0;
}
}
按思路2解决
target=7,可以选择2,3,6,7任意一个元素,也就是candidates中下标从0到4的任意一个元素。选择2,list添加2,进入状态target=5。
target=5,可以选择2,3。因为6,7大于5,不用选择剪枝。选择2,list添加2,进入target=3。
target=3,可以选择2,3。因为6,7大于3,不用选择剪枝。选择2,list添加2,进入target=1。
…
退出条件:当target=0,问题解决,添加结果集。
按照这个思路画递归树,发现有重复的解。可以通过限制下标起点解决。例如图中选择3之后,可以选择的元素就在3,6,7之间,因为3,6,7都大于2,所以就不用继续递归了。所以递归树上的状态需要加上起始下标。修改递归树。
public List<List<Integer>> combinationSumV3(int[] candidates, int target) {
result.clear();
Arrays.sort(candidates);
this.candidates = candidates;
if(candidates==null || candidates.length==0) return result;
dfsV3(0,target,new ArrayList<Integer>());
return result;
}
/**
* 在当前状态下,可以选择从start到数组结尾的任意一个元素到结果集
*
* @param start
* @param target
* @param list
*/
private void dfsV3(int start, int target,ArrayList<Integer> list) {
if(target == 0) {
//注意结果需要完全拷贝
result.add(new ArrayList<Integer>(list));
return;
}
if(start>= candidates.length) return;
for(int i = start;i<candidates.length;i++){
if(candidates[i] > target) break;
list.add(candidates[i]);
dfsV3(i,target-candidates[i],list);
list.remove(list.size() - 1);
}
}
每一次dfs是树的一个高度。