这是我参与11月更文挑战的第27天,活动详情查看:2021最后一次更文挑战
今日题目
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
注意:解集不能包含重复的组合。
示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8, 输出: [ [1,1,6], [1,2,5], [1,7], [2,6] ]
示例 2:
输入: candidates = [2,5,2,1,2], target = 5, 输出: [ [1,2,2], [5] ]
提示:
1 <= candidates.length <= 100 1 <= candidates[i] <= 50 1 <= target <= 30
思路
这道题相较于组合总和1的区别是:
- 1、每个元素不能再无限使用,而是只能用一次,因此每次递归的下标不能再从当前小标开始,而是i+1开始。
- 2、元素有重复元素,因此需要去重。
上述两点都有去重的效果,一个是在结果上保证了没有重复元素,一个是保证在横向遍历的时候保证没有重复搜索。 除此之外与大多数回溯思路一样。
递归遍历数组,每次首先判断当前下标元素是否与前一个元素相同,如果相同同时判断上一个元素是否正在使用,如果没有使用则直接跳过,因为上一个元素前面已经遍历过了。
然后判断当前元素是否小于等于target,如果大于target,则说明如果target减去当前元素差一定小于0,不满足等于0,直接break循环就可以了。因为该元素之后的元素一定是大于等于当前元素的,因此target减去该元素的差也一定是小于0的,所以直接跳过。这是一步非常重要的剪枝操作。
将当前元素加入path列表,将当前vis[i]设置为true表示已访问。
进入递归,判断target是否为0,如果为0,加入答案列表。
出递归,需要进行回溯,将当前元素从path列表中删除,并将访问置为false。
总结成两个问题:
- 1.如何保证每个数字在每个组合中只使用一次? 答:每次递归调用方法时,从当前数的下一个数开始
- 2.如何保证解集不能包含重复的组合? 答:在有序数组中,如果索引i处的数字 等于 索引i-1处的数字,就直接跳过i,进入下一轮循环
还是使用如下框架代码:
result = []
backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择
复制代码
代码实现:
List<List<Integer>> result = new LinkedList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
List<Integer> list = new ArrayList<>();
Arrays.sort(candidates);
combinationList(candidates, 0, target, list);
return result;
}
public void combinationList(int[] candidates, int start, int target, List<Integer> list{
if (target == 0) {
result.add(new ArrayList<>(list));
return;
}
for (int i = start; i < candidates.length; i++) {
// 剪裁情况一
if (target - candidates[i] < 0) {
break;
}
// 剪裁情况二
if (i > start && candidates[i] == candidates[i - 1]) {
continue;
}
list.add(candidates[i]);
// 此处为i+1,因为元素不可重复使用,当用start+1时,会导致重复计算
combinationList(candidates, i + 1, target - candidates[i], list);
// 回溯
list.removeLast();
}
}
复制代码
执行用时:2 ms, 在所有 Java 提交中击败了99.32%的用户
内存消耗:38.4 MB, 在所有 Java 提交中击败了85.81%的用户
小结
回溯算法解题整体都是有着类似的目的,都是在寻找所有可行解的题,我们都可以尝试用搜索回溯的方法来解决。同时要记住算法实现的基本框架,可以大大减少实现难度。