關於回溯有一個很好的文章
【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);
}
}