知識點:遞歸;回溯;組合;剪枝算法
給定一個數組 candidates 和一個目標數 target ,找出 candidates 中全部可使數字和爲 target 的組合。數組
candidates 中的每一個數字在每一個組合中只能使用一次。函數
注意:解集不能包含重複的組合。ui
輸入: candidates = [10,1,2,7,6,1,5], target = 8, 輸出: [ [1,1,6], [1,2,5], [1,7], [2,6] ] 輸入: candidates = [2,5,2,1,2], target = 5, 輸出: [ [1,2,2], [5] ]
回溯算法的模板:code
result = [] //結果集 def backtrack(路徑, 選擇列表): if 知足結束條件: result.add(路徑) //把已經作出的選擇添加到結果集; return //通常的回溯函數返回值都是空; for 選擇 in 選擇列表: //其實每一個題的不一樣很大程度上體如今選擇列表上,要注意這個列表的更新, //好比多是搜索起點和重點,好比多是已經達到某個條件,好比可能已經選過了不能再選; 作選擇 //把新的選擇添加到路徑裏;路徑.add(選擇) backtrack(路徑, 選擇列表) //遞歸; 撤銷選擇 //回溯的過程;路徑.remove(選擇)
核心就是for循環裏的遞歸,在遞歸以前作選擇,在遞歸以後撤銷選擇;排序
本題又和39題不同了,咱們來看一下不同的點在哪裏遞歸
因此使用一個used數組來標記那個元素被選擇過。candidates[i] == candidates[i - 1] 而且 used[i - 1] == false,就說明:前一個樹枝,使用了candidates[i - 1],也就是說同一樹層使用過candidates[i - 1]。leetcode
能夠看出在candidates[i] == candidates[i - 1]相同的狀況下:rem
class Solution { public List<List<Integer>> combinationSum2(int[] candidates, int target) { List<List<Integer>> res = new ArrayList<>(); Stack<Integer> path = new Stack<>(); boolean[] used = new boolean[candidates.length]; //標記誰被選擇過; Arrays.sort(candidates); backtrack(candidates, target, 0, 0, res, path, used); return res; } private void backtrack(int[] candidates, int target, int begin, int sum, List<List<Integer>> res, Stack<Integer> path, boolean[] used){ if(sum == target){ res.add(new ArrayList<>(path)); return; } for(int i = begin; i < candidates.length && sum + candidates[i] <= target; i++){ if(i > 0 && candidates[i] == candidates[i-1] && !used[i-1]) { continue; //前一樹枝用過了; } //作選擇 sum += candidates[i]; path.push(candidates[i]); used[i] = true; //遞歸:開始下一輪選擇; backtrack(candidates, target, i+1, sum, res, path, used); //撤銷選擇:回溯; sum -= candidates[i]; path.pop(); used[i] = false; } } }
組合總和2get