剑指 Offer II 082. 含有重复元素集合的组合(40. 组合总和 II)

题目:

 

 

思路:

【1】借鉴  剑指 Offer II 081. 允许重复选择元素的组合(39. 组合总和)这道题。首先先从题目看会发现高度重合,区别在于,一个可以重复选,一个不可以重复选,其次是一个是去重的数组,一个是不去重的数组。那么改起来也很简单。

【2】先对数组进行去重,再进行回溯的方式

代码展示:

先对数组进行去重,再进行回溯的方式:

//时间3 ms击败76.64%
//内存41.7 MB击败74.78%
class Solution {
    List<int[]> freq = new ArrayList<int[]>();
    List<List<Integer>> ans = new ArrayList<List<Integer>>();
    List<Integer> sequence = new ArrayList<Integer>();

    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        for (int num : candidates) {
            int size = freq.size();
            if (freq.isEmpty() || num != freq.get(size - 1)[0]) {
                freq.add(new int[]{num, 1});
            } else {
                ++freq.get(size - 1)[1];
            }
        }
        dfs(0, target);
        return ans;
    }

    public void dfs(int pos, int rest) {
        if (rest == 0) {
            ans.add(new ArrayList<Integer>(sequence));
            return;
        }
        if (pos == freq.size() || rest < freq.get(pos)[0]) {
            return;
        }

        dfs(pos + 1, rest);

        int most = Math.min(rest / freq.get(pos)[0], freq.get(pos)[1]);
        for (int i = 1; i <= most; ++i) {
            sequence.add(freq.get(pos)[0]);
            dfs(pos + 1, rest - i * freq.get(pos)[0]);
        }
        for (int i = 1; i <= most; ++i) {
            sequence.remove(sequence.size() - 1);
        }
    }
}

 

借鉴进行修改的方式:

//时间4 ms击败61.9%
//内存41.7 MB击败66.86%
class Solution {
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        int len = candidates.length;
        List<List<Integer>> res = new ArrayList<>();
        if (len == 0) {
            return res;
        }
        Arrays.sort(candidates);
        Deque<Integer> path = new ArrayDeque<>();
        dfs(candidates, 0, len, target, path, res);
        return res;
    }

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

        // 重点理解这里从 begin 开始搜索的语意
        for (int i = begin; i < len; i++) {
            //其次这里也是为了组合去重,因为表示在树的同一层中只会出现该数字一次
            //避免 [10,1,2,7,6,1,5] 这种会出现两个 [1,7]的情况
            if(i > begin && candidates[i] == candidates[i-1]) continue;
            path.addLast(candidates[i]);

            // 注意:由于每一个不元素可以重复使用,下一轮搜索的起点是 i + 1。
            dfs(candidates, i + 1, len, target - candidates[i], path, res);

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

 

posted @ 2023-04-03 15:39  忧愁的chafry  阅读(22)  评论(0编辑  收藏  举报