知識點:遞歸;回溯;組合;剪枝java
給定一個無重複元素的正整數數組 candidates 和一個正整數 target ,找出 candidates 中全部可使數字和爲目標數 target 的惟一組合。python
candidates 中的數字能夠無限制重複被選取。若是至少一個所選數字數量不一樣,則兩種組合是惟一的。算法
對於給定的輸入,保證和爲 target 的惟一組合數少於 150 個。數組
輸入: candidates = [2,3,6,7], target = 7 輸出: [[7],[2,2,3]] 輸入: candidates = [2,3,5], target = 8 輸出: [[2,2,2,2],[2,3,3],[3,5]] 輸入: candidates = [2], target = 1 輸出: [] 輸入: candidates = [1], target = 1 輸出: [[1]] 輸入: candidates = [1], target = 2 輸出: [[1,1]]
回溯算法的模板:函數
result = [] //結果集 def backtrack(路徑, 選擇列表): if 知足結束條件: result.add(路徑) //把已經作出的選擇添加到結果集; return //通常的回溯函數返回值都是空; for 選擇 in 選擇列表: //其實每一個題的不一樣很大程度上體如今選擇列表上,要注意這個列表的更新, //好比多是搜索起點和重點,好比多是已經達到某個條件,好比可能已經選過了不能再選; 作選擇 //把新的選擇添加到路徑裏;路徑.add(選擇) backtrack(路徑, 選擇列表) //遞歸; 撤銷選擇 //回溯的過程;路徑.remove(選擇)
核心就是for循環裏的遞歸,在遞歸以前作選擇,在遞歸以後撤銷選擇;優化
對於本題,有兩點和77題組合不同:ui
咱們換個角度從新畫這個圖,和77題有點差距,理解的更全面一點。 其實這就是一個橫向循環和縱向的遞歸,橫向循環作出不一樣的選擇,縱向在不一樣的選擇基礎上作下一步選擇。code
class Solution { public List<List<Integer>> combinationSum(int[] candidates, int target) { List<List<Integer>> res = new ArrayList<>(); Stack<Integer> path = new Stack<>(); backtrack(candidates, target, 0, 0, res, path); return res; } private void backtrack(int[] candidates, int target, int sum, int begin, List<List<Integer>> res, Stack<Integer> path){ if(sum > target){ return; } if(sum == target){ res.add(new ArrayList<>(path)); return; } for(int i = begin; i < candidates.length; i++){ //作選擇; sum += candidates[i]; path.push(candidates[i]); //遞歸:開始下一輪選擇; backtrack(candidates, target, sum, i, res, path); //不用+1,能夠重複選; //撤銷選擇:回溯 sum -= candidates[i]; path.pop(); } } }
上述程序有優化的空間,咱們能夠對數組先進行排序,而後若是找到了當前的sum已經等於target或大於target了,那後面的就能夠直接跳過了,由於後面的元素更大,確定更大於target。排序
class Solution { public List<List<Integer>> combinationSum(int[] candidates, int target) { List<List<Integer>> res = new ArrayList<>(); Stack<Integer> path = new Stack<>(); Arrays.sort(candidates); //排序 backtrack(candidates, target, 0, 0, res, path); return res; } private void backtrack(int[] candidates, int target, int sum, int begin, List<List<Integer>> res, Stack<Integer> path){ if(sum == target){ res.add(new ArrayList<>(path)); return; } for(int i = begin; i < candidates.length && sum + candidates[i] <= target; i++){ //剪枝:若是sum+candidates[i] > target就結束; //作選擇; sum += candidates[i]; path.push(candidates[i]); //遞歸:開始下一輪選擇; backtrack(candidates, target, sum, i, res, path); //不用+1,能夠重複選; //撤銷選擇:回溯 sum -= candidates[i]; path.pop(); } } }