回溯-组合
组合问题也是需要进行穷举的,使用回溯算法正合适。
案例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); } } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
2021-11-01 python解决线性规划问题
2021-11-01 python解决多变量最优化问题
2021-11-01 python处理单变量优化
2021-11-01 java枚举类常用方法