题目

类型:回溯

难度:中等

组合总和 II - 图1

解题思路

使用递归 + 回溯

与第 39 题(组合之和)的差别在于这题每个数字在每个组合中只能使用一次,相同点是相同数字列表的不同排列视为一个结果。

由上述分析可以得出,如何去重是重点

两种方法,一种是使用哈希表,这种方法比较复杂。第二种方法就用和39题一样的思路,我们先排序,在搜索的过程中检测分支是不是有重复结果。

组合总和 II - 图2

代码

  1. package backtracking;
  2. import java.util.*;
  3. public class CombinationSumII {
  4. public List<List<Integer>> combinationSum2(int[] candidates, int target) {
  5. int len = candidates.length;
  6. List<List<Integer>> res = new ArrayList<>();
  7. if (len == 0) {
  8. return res;
  9. }
  10. // 关键步骤
  11. Arrays.sort(candidates);
  12. Deque<Integer> path = new ArrayDeque<>(len);
  13. dfs(candidates, len, 0, target, path, res);
  14. return res;
  15. }
  16. /**
  17. * @param candidates 候选数组
  18. * @param len 冗余变量
  19. * @param begin 从候选数组的 begin 位置开始搜索
  20. * @param target 表示剩余,这个值一开始等于 target,基于题目中说明的"所有数字(包括目标数)都是正整数"这个条件
  21. * @param path 从根结点到叶子结点的路径
  22. * @param res
  23. */
  24. private void dfs(int[] candidates, int len, int begin, int target, Deque<Integer> path, List<List<Integer>> res) {
  25. if (target == 0) {
  26. res.add(new ArrayList<>(path));
  27. return;
  28. }
  29. for (int i = begin; i < len; i++) {
  30. // 大剪枝:减去 candidates[i] 小于 0,减去后面的 candidates[i + 1]、candidates[i + 2] 肯定也小于 0,因此用 break
  31. if (target - candidates[i] < 0) {
  32. break;
  33. }
  34. // 小剪枝:同一层相同数值的结点,从第 2 个开始,候选数更少,结果一定发生重复,因此跳过,用 continue
  35. if (i > begin && candidates[i] == candidates[i - 1]) {
  36. continue;
  37. }
  38. path.addLast(candidates[i]);
  39. // 调试语句 ①
  40. // System.out.println("递归之前 => " + path + ",剩余 = " + (target - candidates[i]));
  41. // 因为元素不可以重复使用,这里递归传递下去的是 i + 1 而不是 i
  42. dfs(candidates, len, i + 1, target - candidates[i], path, res);
  43. path.removeLast();
  44. // 调试语句 ②
  45. // System.out.println("递归之后 => " + path + ",剩余 = " + (target - candidates[i]));
  46. }
  47. }
  48. }