前言
很少写关于leetcode相关算法的解法,这篇则是由于回溯的剪枝策略过于优秀,我在一段时间都没想到,于是感觉可以记录下来,加深自己印象的同时分享一下。
正文
题目就是leetcode的第40道题,组合总和2,描述如下:
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
说明:
所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。
示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
示例 2:
输入: candidates = [2,5,2,1,2], target = 5,
所求解集为:
[
[1,2,2],
[5]
]
解题思路
首先我们在进行题目解法思考时,基本上都可以考虑到这是一个回溯算法,应该使用dfs解法;其次又要考虑到去重,因此一个“排序”数组对于我们是很有用的,在去重时一个最简单的方案就是对于生成的数组进行内部排序对比去重,当然这样又low,效率又低,因此我们需要有一个灵活的剪枝操作。如下图所示:
如上图所示,以下几个步骤来解决:
- 我们在对原始数组 [2,5,2,1,2]进行排序后得到[1,2,2,2,5],然后进行dfs的回溯遍历;
- 接着出现了第一个剪枝的操作,因为我们是排序的数组,所以如果后续的总和比我当前的index数字还要大的话,就不需要进行下一个部分了。
- 然后我们还要进行相同数字所造成答案中重复数组的剪枝去重操作,这块是我认为最难的一个点,我们如何能保证以下两点是关键:
1
/ \
2 2 这种A情况不会发生 但是却允许了不同层级之间的重复即:
/ \
5 5
1
/
2 这种B情况确是允许的
/
2
那么我们如何能避免A情况,candidates[i] == candidates[i - 1]时跳过,但是这样也会是B情况被跳过,A和B的区别在哪,就是在于A和B的重复元素的位置不一样,B在于重复元素不在同一层,因此我们再加一个条件,当进行dfs for循环时,i > index层数不在一层时进行跳过,代码如下:
public class Solution {
public static void main(String[] args) {
int[] arr1 = {10, 1, 2, 7, 6, 1, 5};
int target1 = 8;
List<List<Integer>> result1 = combinationSum2(arr1, target1);
int[] arr2 = {2, 5, 2, 1, 2};
int target2 = 5;
List<List<Integer>> result2 = combinationSum2(arr2, target2);
System.out.println();
}
public static List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
List<List<Integer>> result = new ArrayList<>();
List<Integer> nowList = new ArrayList<>();
int index = 0;
dfs(result, nowList, index, target, candidates);
//dfs时对重复数据进行剪枝操作。
return result;
}
private static void dfs(List<List<Integer>> result, List<Integer> nowList, int index, int target, int[] candidates) {
if (target == 0) {
result.add(new ArrayList<>(nowList));
return;
}
for (int i = index; i < candidates.length; i++) {
if (i > index && candidates[i] == candidates[i - 1]) {
continue;
}
if (target < candidates[i]) {
break;
}
target = target - candidates[i];
nowList.add(candidates[i]);
dfs(result, nowList, i + 1, target, candidates);
nowList.remove(nowList.size() - 1);
target = target + candidates[i];
}
}
}
结语
在一些排列组合等dfs回溯算法时,进行剪枝操作都会比较清晰、条件比较容易得出,这个题目的剪枝相对比较巧妙,因此记录下来,在考虑剪枝防止剪到正常枝叶时,需要对几种情况综合对比考虑即可。
参考:https://leetcode-cn.com/problems/combination-sum-ii/solution/hui-su-suan-fa-jian-zhi-python-dai-ma-java-dai-m-3/