组合问题也是需要进行穷举的,使用回溯算法正合适。
案例1:
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
注意:一般的组合中,数字是不能重复使用的,这个题目很特殊,数字可以重复使用。
1.可行解的要求是什么?
显而易见,我们找到了数组中的数字组合,加起来的和正好等于目标target,这就是一个可行解
2.递归退出的条件是什么?
有三个,其中两个是明确的①找到了可行解,②递归过程中发现target<0,
隐含的退出是for循环结束。
3.如何进行回溯
我们使用了path队列存储过程解,当需要回溯时,删掉path的最后一个元素即可。
4.如何求得不同的组合
题目中虽然明确说明元素可以重复使用,但是组合肯定是不能重复的,比如我们已经找到一个[2,2,3]作为可行解,在后面不能又找到一个[3,2,2]这样组合就重复了。
解决办法是我们定义一个begin指针,下层递归只能使用大于等于begin的元素,就不会出现上面的情况了。
class Solution { List<List<Integer>> res = new ArrayList<>(); /** * 大佬的题解比官方的更容易理解!! * @param candidates * @param target * @return */ public List<List<Integer>> combinationSum(int[] candidates, int target) { int len = candidates.length; if (len == 0) { return res; } //阶段性成果 Deque<Integer> path = new ArrayDeque<>(); //深度优先搜索 dfs(candidates, target, path, 0); return res; } /** * @param candidates 候选数组 * @param target 每减去一个元素,目标值变小 * @param path 从根结点到叶子结点的路径,是一个栈 * @param begin 搜索起点 */ private void dfs(int[] candidates, int target, Deque<Integer> path, int begin) { // target 为负数和 0 的时候不再产生新的孩子结点 int len = candidates.length; if (target < 0) { return; } //产生一个有效结果 if (target == 0) { res.add(new ArrayList<>(path)); return; } // 重点理解这里从 begin 开始搜索的语意 for (int i = begin; i < len; i++) { path.addLast(candidates[i]); // 注意:由于每一个元素可以重复使用,下一轮搜索的起点依然是 i,这里非常容易弄错 dfs(candidates, target - candidates[i], path, i); // 状态重置 path.removeLast(); } } }
案例2:
给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。
本题目和上一题目非常类似,区别在于
重点:上一个题目每个元素可以用多次,这个只能用一次,而且不能包含重复的组合。
1.如何保证一个元素只使用一次
我们定义一个begin指针,下一次取数据的时候指针+1
2.如何在元素有重复的情况下保证组合是不重复的
这一点非常非常非常关键!也是难点所在!
方法就是在同一层遍历的时候,如果发现当前元素和前一个元素相同,比如223遍历到第二个2的时候,发现前面也是2,那么这个地方就需要进行剪枝。
class Solution { List<List<Integer>> ans = new ArrayList<>(); public List<List<Integer>> combinationSum2(int[] candidates, int target) { List<Integer> path = new ArrayList<>(); Arrays.sort(candidates); dfs(candidates, path, target, 0); return ans; } private void dfs(int[] candidates, List<Integer> path, int target, int begin) { if (target < 0) { return; } if (target == 0) { ans.add(new ArrayList<>(path)); return; } int len = candidates.length; if (begin == len) { return; } for (int i = begin; i < len; i++) { //这一条的剪枝是关键!!! if (i > begin && candidates[i] == candidates[i - 1]) { continue; } path.add(candidates[i]); dfs(candidates, path, target - candidates[i], i + 1); path.remove(path.size() - 1); } } }
标签:begin,return,target,组合,int,candidates,回溯,path From: https://www.cnblogs.com/wangbin2188/p/16848831.html