知识点:递归;回溯;组合;剪枝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(); } } }