递归与回溯7:LeetCode40. 组合总和 II(不可重复)

本题与LeetCode39的区别就是这里candidates 中的每个元素在每个组合中只能使用 一次 。但是candidats数组里的元素是可以重复的。有点绕,我们看一下例子:

示例1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]

题目说candidates是一个数组,里面有两个1,因此输出的结果里可以出现{1,1}的情况。严格来说这里说candidates是一个集合,是不严谨的,因为集合里元素是不能重复的。我们理解就好。

这个题与39题都相同的一点就是解集不能包含重复的组合。例如,不能出现一个解为{1,2,5},另一个为{5,2,1}的情况。

这个题的关键在于如何去重,就是使用过的元素不能重复选取。 这里补充一点,“使用过”在回溯的树形结构上是有两个维度的,一个维度是同一树枝上使用过,一个维度是同一树层上使用过。这两个层面上的“使用过” 很明显是不一样的。

那么,我们是要同一树层上使用过,还是同一树枝上使用过呢?看一下题目,元素在同一个组合内是可以重复的,怎么重复都没事,但两个组合不能相同。所以我们要去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重。

这里为了好处理,我们最好先将所有元素排序一下,假如candidates = [1, 1, 2], target = 3,Carl画的这个图非常形象 ,我们直接拿过来看一看:

 可以看到图中,每个节点都多加了used数组,这个就是去重的关键。要去重的是“同一树层上的使用过”,如果判断同一树层上元素(相同的元素)是否使用过了呢。

如果candidates[i] == candidates[i - 1] 并且 used[i - 1] == false,就说明前一个树枝,使用了candidates[i - 1],也就是说同一树层使用过candidates[i - 1]。此时for循环里就应该做continue的操作。如下图所示:(引自carl博客)

图中将used的变化用橘黄色标注上,可以看出在candidates[i] == candidates[i - 1]相同的情况下:

  •  used[i - 1] == true,说明同一树支candidates[i - 1]使用
  •  used[i - 1] == false,说明同一树层candidates[i - 1]使用过

 其他部分的处理方式与39题基本一致,完整实现如下:

class Solution {
    List<List<Integer>> lists = new ArrayList<>();
    Deque<Integer> deque = new LinkedList<>();
    int sum = 0;

    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        //为了好处理重复,所以先排序,这里可以直接调基础方法
        Arrays.sort(candidates);
        //加标志数组,用来辅助判断同层节点是否已经遍历
        boolean[] used = new boolean[candidates.length];
        backTracking(candidates, target, 0, used);
        return lists;
    }

    public void backTracking(int[] arr, int target, int index, boolean[] used) {
        if (sum == target) {
            lists.add(new ArrayList(deque));
            return;
        }
        for (int i = index; i < arr.length && arr[i] + sum <= target; i++) {
            //出现重复节点,同层的第一个节点已经被访问过,所以直接跳过
            if (i > 0 && arr[i] == arr[i - 1] && !used[i - 1]) {
                continue;
            }
            used[i] = true;
            sum += arr[i];
            deque.push(arr[i]);
            //每个节点仅能选择一次,所以从下一位开始
            backTracking(arr, target, i + 1, used);
            int temp = deque.pop();
            used[i] = false;
            sum -= temp;
        }
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纵横千里,捭阖四方

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值