回溯-组合

组合问题也是需要进行穷举的,使用回溯算法正合适。

案例1:

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。 

注意:一般的组合中,数字是不能重复使用的,这个题目很特殊,数字可以重复使用。

1.可行解的要求是什么?

显而易见,我们找到了数组中的数字组合,加起来的和正好等于目标target,这就是一个可行解

2.递归退出的条件是什么?

有三个,其中两个是明确的①找到了可行解,②递归过程中发现target<0,

隐含的退出是for循环结束。

3.如何进行回溯

我们使用了path队列存储过程解,当需要回溯时,删掉path的最后一个元素即可。

4.如何求得不同的组合

题目中虽然明确说明元素可以重复使用,但是组合肯定是不能重复的,比如我们已经找到一个[2,2,3]作为可行解,在后面不能又找到一个[3,2,2]这样组合就重复了。

解决办法是我们定义一个begin指针,下层递归只能使用大于等于begin的元素,就不会出现上面的情况了。

class Solution {
   List<List<Integer>> res = new ArrayList<>();

    /**
     * 大佬的题解比官方的更容易理解!!
     * @param candidates
     * @param target
     * @return
     */
    public List<List<Integer>> combinationSum(int[] candidates, int target) {

        int len = candidates.length;
        if (len == 0) {
            return res;
        }
        //阶段性成果
        Deque<Integer> path = new ArrayDeque<>();
        //深度优先搜索
        dfs(candidates, target, path, 0);
        return res;
    }

    /**
     * @param candidates 候选数组
     * @param target     每减去一个元素,目标值变小
     * @param path       从根结点到叶子结点的路径,是一个栈
     * @param begin      搜索起点
     */
    private void dfs(int[] candidates, int target, Deque<Integer> path, int begin) {
        // target 为负数和 0 的时候不再产生新的孩子结点
        int len = candidates.length;
        if (target < 0) {
            return;
        }
        //产生一个有效结果
        if (target == 0) {
            res.add(new ArrayList<>(path));
            return;
        }

        // 重点理解这里从 begin 开始搜索的语意
        for (int i = begin; i < len; i++) {
            path.addLast(candidates[i]);

            // 注意:由于每一个元素可以重复使用,下一轮搜索的起点依然是 i,这里非常容易弄错
            dfs(candidates, target - candidates[i], path, i);

            // 状态重置
            path.removeLast();
        }
    }
}

案例2:

给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用 一次 。

注意:解集不能包含重复的组合。

本题目和上一题目非常类似,区别在于 

重点:上一个题目每个元素可以用多次,这个只能用一次,而且不能包含重复的组合。

1.如何保证一个元素只使用一次

我们定义一个begin指针,下一次取数据的时候指针+1

2.如何在元素有重复的情况下保证组合是不重复的

这一点非常非常非常关键!也是难点所在!

方法就是在同一层遍历的时候,如果发现当前元素和前一个元素相同,比如223遍历到第二个2的时候,发现前面也是2,那么这个地方就需要进行剪枝。

class Solution {
    List<List<Integer>> ans = new ArrayList<>();

    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        List<Integer> path = new ArrayList<>();
        Arrays.sort(candidates);
        dfs(candidates, path, target, 0);
        return ans;
    }

    private void dfs(int[] candidates, List<Integer> path, int target, int begin) {
        if (target < 0) {
            return;
        }
        if (target == 0) {
            ans.add(new ArrayList<>(path));
            return;
        }
        int len = candidates.length;
        if (begin == len) {
            return;
        }

        for (int i = begin; i < len; i++) {
            //这一条的剪枝是关键!!!
            if (i > begin && candidates[i] == candidates[i - 1]) {
                continue;
            }
            path.add(candidates[i]);
            dfs(candidates, path, target - candidates[i], i + 1);
            path.remove(path.size() - 1);
        }
    }
}

 

posted @ 2022-11-01 19:03  Mars.wang  阅读(37)  评论(0编辑  收藏  举报