這道題,採用
遞歸+回溯
,關鍵是需要進行剪枝
,解集
中不能包含重複組合,怎麼剪枝
呢,畫出隱形回溯樹
可以發現,若同一層的遞歸數據相等則除了相同數據第一次出現外,其他則跳過這條路徑(必須基於數組是排序的)
;continu
爲小剪枝,break
爲大剪枝
package BDyNamicProgramming;
import sun.reflect.generics.tree.Tree;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/4/26 0026 13:29
*/
public class Problem40 {
/**
*
* @param candidates
* @param target
* @return
* 這道題與上一問的區別在於:
*
* 第39題:candidates中的數字可以無限制重複被選取
* 第40題:candidates中的每個數字在每個組合中只能使用一次
*
* 編碼的不同之處在於下一層遞歸的起始索引不一樣:
* 第39題:還從候選數組的當前索引值開始
* 第40題:從候選數組的當前索引值的下一位開始
*/
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
List<List<Integer>> rs = new ArrayList<>();
List<Integer> path = new ArrayList<>();
//對數組進行排序:排序視爲了提前終止搜索,進行剪枝
Arrays.sort(candidates);
//標註:數組中哪些元素被使用到了:方便去重
// boolean[] used = new boolean[candidates.length];
dfs(candidates,target,path,rs,0);
return rs;
}
public void dfs(int[] candidates,int target,List<Integer> path,List<List<Integer>> rs,int start){
//符合值相等
if(target==0){
//由於r全局只適用到一份,到葉子節點的時候需要左一份拷貝
rs.add(new ArrayList<>(path));
}
if(target<0) return;
//遞歸加回溯
//在當前的選擇下,從start開始進行選擇.start(包含)之後的位置都可以進行選擇
for(int i=start;i<candidates.length;i++){
//數組中包含重複的元素必須進行去重
//剪枝檢測到重複分支的條件:
//1、不是這層的第一個分支
//2、當前選出的數和前一個分支相等
//這個避免重複思想比較重要
//這個方法最重要的作用是,可以讓同一層級,不出現相同的元素
//不同級的元素可以出現重複的元素
//continue
if(i>start&&candidates[i]==candidates[i-1]) continue;
//大減職業,因爲數組已經進行排序了,當前數據大於target則之後的數必然也大於
//直接跳出剪枝即可
if(candidates[i]>target) break;
path.add(candidates[i]);
//下輪搜索的起點爲i+1,因爲元素不可以重複使用,這裏遞歸傳遞下去的是i+1而不是i
dfs(candidates,target-candidates[i],path,rs,i+1);
//進行回溯
path.remove((Object)candidates[i]);
}
}
}