关于回溯有一个很好的文章
【https://leetcode-cn.com/problems/subsets/solution/xiang-xi-jie-shao-di-gui-hui-su-de-tao-lu-by-reedf/】
回溯法有一个书写模板,大致如下:
1. 一定范围的解释:根据具体的题目意思,决定范围的起止位置。
一般结束的位置是集合的末尾,起始位置可能是0、first、first+1,具体的还是要根据题目,可能配合visited数组更加方便理解。
void func(index, currAns, answer)
if(达到终止条件){
//判断当前结果是否符合条件
answer.add(currAns)
}
//当前结点及同一层次的其它未搜索结点
//使用循环扫描
for(i: 一定范围){
//将当前结点加入解
currAns.add(第index个结点)
//对第index结点的子结构进行递归
func(index+1, currAns, answer)
//将第index结点移出解
currAns.add(第index个结点)
}
}
题目:leetcode 46 全排列问题
给定nums数组,输出元素的全排列。
起止位置的决定:全排列问题不允许元素重用,使用visited数组。
代码如下:
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> answer=new ArrayList<>();
if(nums==null||nums.length==0) return answer;
List<Integer> numsList=new ArrayList<>();
boolean[] visited=new boolean[nums.length];
//转换为list
for(int i=0;i<nums.length;i++){
numsList.add(nums[i]);
}
permuteCore(numsList,visited,new ArrayList<>(),answer);
return answer;
}
public void permuteCore(List<Integer> numsList,boolean[] visited, List<Integer> currAns, List<List<Integer>> answer){
if (currAns.size() == numsList.size()) {
//两个size相等,说明得到了一个全排列,重新包装一下放到答案集合
answer.add(new ArrayList<>(currAns));
return;
}
//注意这里的边界,因为是全排列,需要分别以每一个元素作为起点
//与子集问题的不同点
for(int i=0;i<numsList.size();i++){
//已经加过的就不要重复添加
if(visited[i]) continue;
currAns.add(numsList.get(i));
visited[i]=true;
permuteCore(numsList,visited,currAns,answer);
currAns.remove(numsList.get(i));
visited[i]=false;
}
}
题目:leetcode 78 子集问题
给定一个集合,输出集合所有子集
代码如下:
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> answer=new ArrayList<>();
if (nums == null || nums.length == 0) {
return answer;
}
subsetsCore(nums,0,answer,new ArrayList<>());
return answer;
}
public void subsetsCore(int[] nums, int index, List<List<Integer>> answer, List<Integer> currentAns) {
//这里比较难一些,为什么每一个遍历到的结点都需要添加到解集合
//因为整个搜索树的所有结点,包括根结点空集,都是一种合法子集
//在回溯到上一个结点之后是不会重复添加的,回溯之后会立刻向下一个结点遍历
answer.add(new ArrayList<>(currentAns));
for(int i=index;i<nums.length;i++){
currentAns.add(nums[i]);
subsetsCore(nums,i+1,answer,currentAns);
currentAns.remove(currentAns.size()-1);
}
}
题目:LeetCode39 组合总和
给定一个元素集合与一个整数target,给出所有可能构成target的元素集合。元素可重用。
这个代码循环的边界确定比较有意思,因为组合的解允许元素重用,但是不允许元素相同排列不同的解存在,所以每次递归循环都从当前元素开始。
代码如下:
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> answer=new ArrayList<>();
if (candidates == null || candidates.length == 0 || target <= 0) {
return answer;
}
Arrays.sort(candidates);
combinationSumCore(0,candidates,target,candidates[0],new ArrayList<>(),answer);
return answer;
}
public void combinationSumCore(int first, int[] cadidates, int target, int min,
List<Integer> currAns, List<List<Integer>> answer){
if(target==0){
answer.add(new ArrayList<>(currAns));
return;
}
if(target<min){
return;
}
for(int i=first;i<cadidates.length;i++){
currAns.add(cadidates[i]);
combinationSumCore(i,cadidates,target-cadidates[i],cadidates[i],currAns,answer);
currAns.remove(currAns.size()-1);
}
}