leetcode40. 组合总和 II

1.题目描述:

给定一个候选人编号的集合candidates和一个目标数target,找出candidates中所有可以使数字和为target的组合。candidates中的每个数字在每个组合中只能使用一次。注意:解集不能包含重复的组合。

leetcode40. 组合总和 II_第1张图片

2.当开始写的回溯代码(已完成剪枝)

类似的题型做多了直接按照模板写。一跑发现有重复的结果。原因是此题数组元素会有重复的,[10,1,2,7,6,1,5]排序后[1,1,2,5,6,7,10],[1,2,5]和[1,7]会出现两次,这里的1两次分别是索引0和索引1的位置,可以在最后的resList中去重但是太麻烦。对数组排序之后,去重只要找到元素相等的地方。会发现[1,1,6]只有一次,也就是纵向/递归往下/树枝上允许出现重复元素,但是横线/同一树层上不允许出现重复,否则就会有我上述跑完结果(同一for循环中若有相同元素,那么符合条件的话必然也是两份)。

class Solution {
    private List> resList = new ArrayList<>();
    private List list = new ArrayList<>();
    private int sum = 0;
    public List> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        backTracking(candidates, target, 0);
        return resList;
    }

    public void backTracking(int[] candidates, int target, int index) {
        if (sum == target) {
            resList.add(new ArrayList<>(list));
            return;
        }

        for (int i = index; i < candidates.length; i++) {
            if (sum + candidates[i] > target) break;//剪枝
            list.add(candidates[i]);
            sum += candidates[i];
            backTracking(candidates, target, i + 1);
            list.remove(list.size() - 1);
            sum -= candidates[i];
        }
    }
}

3.利用index去重(推荐):

class Solution {
    private List> resList = new ArrayList<>();
    private List list = new ArrayList<>();
    private int sum = 0;
    public List> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        backTracking(candidates, target, 0);
        return resList;
    }

    public void backTracking(int[] candidates, int target, int index) {
        if (sum == target) {
            resList.add(new ArrayList<>(list));
            return;
        }

        for (int i = index; i < candidates.length; i++) {
            if (i > index && candidates[i] == candidates[i - 1]) continue;//去重,i > index,防止空指针也防止[1,1,6]放不进结果集,不能i > 0开始;这里操作candidates[i - 1]树枝时已经把一份结果全存入
            if (sum + candidates[i] > target) break;//剪枝
            list.add(candidates[i]);
            sum += candidates[i];
            backTracking(candidates, target, i + 1);
            list.remove(list.size() - 1);
            sum -= candidates[i];
        }
    }
}

二刷:

class Solution {
    List> res = new ArrayList<>();
    List list = new ArrayList<>();
    public List> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        backTracking(candidates, target, 0);
        return res;
    }
    public void backTracking(int[] candidates, int target, int index) {
        int sum = 0;
        for (Integer item : list) sum += item;
        if (sum == target) res.add(new ArrayList<>(list));
        if (sum >= target) return;
        for (int i = index; i < candidates.length && sum + candidates[i] <= target; i++) {
            if (i > index && candidates[i] == candidates[i - 1]) continue;
            list.add(candidates[i]);
            backTracking(candidates, target, i + 1);
            list.remove(list.size() - 1);
        }
    }
}

4.使用标记数组去重:

class Solution {
    private List> resList = new ArrayList<>();
    private List list = new ArrayList<>();
    private int sum = 0;
    private boolean[] flag;
    public List> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        flag = new boolean[candidates.length];//标记数组帮助判断同一树层元素是否已经被访问
        backTracking(candidates, target, 0);
        return resList;
    }

    public void backTracking(int[] candidates, int target, int index) {
        if (sum == target) {
            resList.add(new ArrayList<>(list));
            return;
        }

        for (int i = index; i < candidates.length; i++) {
            if (i > 0 && candidates[i] == candidates[i - 1] && !flag[i - 1]) continue;//去重,这里i > 0没问题,因为有标记的判断
            //flag[i - 1] == true,说明纵向同一树枝candidates[i - 1]使用过
            //flag[i - 1] == false,说明横向同一树层candidates[i - 1]使用过,但是被回溯复原
            if (sum + candidates[i] > target) break;//剪枝
            list.add(candidates[i]);
            flag[i] = true;
            sum += candidates[i];
            backTracking(candidates, target, i + 1);
            list.remove(list.size() - 1);//回溯
            sum -= candidates[i];//回溯
            flag[i] = false;//回溯
        }
    }
}

5.另一种使用标记数组去重(推荐):

上一种标记数组记录了位置而非值(排序后的原因),这种依据每个位置的值(无需排序,排序为了剪枝)来去重,和leetcode491. 递增子序列去重方法一致,但是子集问题必须先排序(不排序会出现虽是同一树层上取不重复值的情况,担仍出现重复子集)!

class Solution {
    private List> resList = new ArrayList<>();
    private List list = new ArrayList<>();
    private int sum = 0;
    public List> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        backTracking(candidates, target, 0);
        return resList;
    }
 
    public void backTracking(int[] candidates, int target, int index) {
        if (sum == target) {
            resList.add(new ArrayList<>(list));
            return;
        }
        boolean[] flag = new boolean[51];//每层更新
        for (int i = index; i < candidates.length; i++) {
            if (flag[candidates[i]]) continue;//去重
            if (sum + candidates[i] > target) break;//剪枝
            list.add(candidates[i]);
            flag[candidates[i]] = true;//无需回溯
            sum += candidates[i];
            backTracking(candidates, target, i + 1);
            list.remove(list.size() - 1);//回溯
            sum -= candidates[i];//回溯
        }
    }
}

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