这是我参与8月更文挑战的第29天,活动详情查看:8月更文挑战
周末总是过得特别快,转眼又到了周日的晚上。白天忙活了一顿午饭,请一些朋友来家里吃饭,现在才有空开电脑做做题,今天继续leetcode,第40题。
题目
给定一个数组 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]
]
思路
跟昨天的题目其实非常类似,也是背包的变种,需要恰好装满的那种背包。今天跟昨天的题目,有2个重要的不同点:
- 每个数字只能使用一次
- 解集不能包含重复的组合 当然,需要注意的是,每个数字只能使用一次,不代表每个数字只能出现1次,因为在原数组中,就可能同一个数字出现多次的情况。 整体上,跟昨天的解法是非常类似的,但是有几个不一样的点:
- 因为每个数字只能使用一次,所以这个数字无论是否使用,递归进行深度优先搜索的时候,下一次就需要遍历i+1
- 解集不能包含重复的组合,所以出现1种解之后,需要跟之前的比较一下是否相同,进行去重 做到这2个点之后,其实已经可以求得正确的解,但是目前的效率还不够,提交后会出现TLE。那怎么加速呢?搜索的时候需要进行一些剪枝,除了昨天说的先对原数组排序,可以得到常数的优化外,还有一个比较重要的优化是,如果同一层循环中,遍历的candidates[i] = candidates[i-1],那么candidates[i]就一定不用被选择,这样可以进行一些剪枝,为什么?你可以这样理解,因为candidates[i] = candidates[i-1],如果选择candidates[i],那为什么不选择candidates[i-1]呢,因为candidates[i-1]被选择后,一定比candidates[i]被选择带来更多的组合,因为candidates[i+1]~candidates[len-1]一定是candidates[i]~candidates[len-1]的完全子集,所以选择candidates[i]组合的结果,一定已经在选择candidates[i-1]的时候已经选择过了。当然,candidates[i]和candidates[i-1]可以都选择,但是这种情况也是在candidates[i-1]的时候已经选择过了。 如图所示,蓝色区域是红色区域的完全子集:
Java版本代码
class Solution {
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
combinationSumList = new ArrayList<>();
stack = new Stack<>();
combinationSumDfs2(candidates, 0, target);
return combinationSumList;
}
private static List<List<Integer>> combinationSumList = null;
private static Stack<Integer> stack = null;
private static void combinationSumDfs2(int[] candidates, int start, int target) {
if (target == 0) {
List<Integer> item = new ArrayList<>(stack);
if (!combinationSumList.contains(item)) {
combinationSumList.add(item);
}
return;
}
int len = candidates.length;
if (start >= len) {
return;
}
for (int i = start; i < len; i++) {
// 同一层循环,相同的数值,第一个已经遍历过的情况下,第二个开始的可能组合数一定是第一次可能组合数的子集,所以结果肯定重复,可以直接剪枝
if (i > start && candidates[i] == candidates[i - 1]) {
continue;
}
if (candidates[i] <= target) {
stack.push(candidates[i]);
combinationSumDfs2(candidates, i + 1, target - candidates[i]);
stack.pop();
} else {
break;
}
}
}
}