算法---LeetCode 40. 组合总和 II

1. 题目

原题链接

给定一个数组 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]
]
Related Topics 数组 回溯算法
613 0

2. 题解

2.1 解法1: 回溯法(DFS)

先画出递归树, 思考每个节点的状态值, 应该包含的变量
这里与 算法——LeetCode39. 组合总和 的区别:
39题:元素可以重复使用,组合不能重复。
本题:元素不可以重复使用,组合不能重复。

所以需要注意以下几点:

  1. 给定的数组可能有重复的元素,先排序,使得重复的数字相邻,方便去重。

  2. for 枚举出选项时,加入下面判断,从而忽略掉同一层重复的选项,避免产生重复的组合。比如[1,2,2,2,5],选了第一个 2,变成 [1,2],它的下一选项也是 2,跳过它,因为如果选它,就还是 [1,2]。

if (i - 1 >= index && candidates[i - 1] == candidates[i]) {
    continue;
}
  1. 当前选择的数字不能和下一个选择的数字重复,给子递归传i+1,避免与当前选的i重复。
dfs(i + 1, remain - candidates[i], paths);

代码:

    class Solution {
        List<List<Integer>> ans;
        int[] candidates;

        public List<List<Integer>> combinationSum2(int[] candidates, int target) {
            ans = new ArrayList<>();
            this.candidates = candidates;
            // 关键步骤, 排序, 去重的关键
            Arrays.sort(candidates);
            dfs(0, target, new ArrayList<>());
            return ans;
        }

        /**
         * @param index  从候选数组的 index 位置开始搜索, 下一个要添加的元素索引
         * @param remain 剩余的目标值
         * @param paths  当前结点包含的元素值,  从根结点到叶子结点的路径
         */
        public void dfs(int index, int remain, List<Integer> paths) {
            if (remain == 0) {
                ans.add(new ArrayList(paths));
                return;
            }

            // 否则向下层递归, 每次 index 节点为未添加的值, 下一层需要添加
            for (int i = index; i < candidates.length; i++) {
                // 因为数组已经排序, 所以后面的也会是负数, 这里不再向下递归, break退出循环
                if (remain - candidates[i] < 0) {
                    break;
                }
                // 重要: 剪枝操作, 由于数组经过了排序, 所以若有重复元素, 选第一个进行递归即可,
                // 后面的舍去不递归, 因为第一个已经包含了后面的情况
                if (i > index && candidates[i] == candidates[i - 1]) {
                    continue;
                }
                paths.add(candidates[i]);
                // 注意这里递归的参数设置
                dfs(i + 1, remain - candidates[i], paths);
                paths.remove(paths.size() - 1);
            }
        }

    }

或者可以将 remain< 放到循环外, 即下一层去做, 但会多递归一层, 代码如下:

    class Solution {
        List<List<Integer>> ans;
        int[] candidates;

        public List<List<Integer>> combinationSum2(int[] candidates, int target) {
            ans = new ArrayList<>();
            this.candidates = candidates;
            // 关键步骤, 排序, 去重的关键
            Arrays.sort(candidates);
            dfs(0, target, new ArrayList<>());
            return ans;
        }

        /**
         * @param index  从候选数组的 index 位置开始搜索, 下一个要添加的元素索引
         * @param remain 剩余的目标值
         * @param paths  当前结点包含的元素值,  从根结点到叶子结点的路径
         */
        public void dfs(int index, int remain, List<Integer> paths) {
            if (remain == 0) {
                ans.add(new ArrayList(paths));
                return;
            }
            if (remain < 0) {
                return;
            }
            // 否则向下层递归, 每次 index 节点为未添加的值, 下一层需要添加
            for (int i = index; i < candidates.length; i++) {
                // 重要: 剪枝操作, 由于数组经过了排序, 所以若有重复元素, 选第一个进行递归即可,
                // 后面的舍去不递归, 因为第一个已经包含了后面的情况
                if (i > index && candidates[i] == candidates[i - 1]) {
                    continue;
                }
                paths.add(candidates[i]);
                // 注意这里递归的参数设置
                dfs(i + 1, remain - candidates[i], paths);
                paths.remove(paths.size() - 1);
            }
        }

    }

参考:
回溯算法 + 剪枝(Java、Python)
手画图解 | 长文漫谈回溯 | 40. 组合总和 II

你可能感兴趣的:(算法,算法,数据结构,java,dfs)