力扣第四十题-组合总和 II

239 阅读2分钟

这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战

前言

力扣第四十题 组合总和 II 如下所示:

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

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

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

示例 1:

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

示例 2:

输入: candidates = [2,5,2,1,2], target = 5,
输出:
[
[1,2,2],
[5]
]

一、思路

这一题和前文的力扣第三十九题-组合总和基本是差不多的,就有一点不同:candidates 中的每个数字在每个组合中只能使用一次。

这一题总体的思路还是通过递归来实现的,但是为了防止组合中的值重复,我们要做三件事情:

  1. candidates 数组排序
  2. 下一次递归的下标为 i + 1
  3. 递归前要防止同级节点的相同值入栈

举个例子

此处以示例1中的 candidates = [10,1,2,7,6,1,5], target = 8 作为例子

  1. 先将数组排序, candidates = [1, 1, 2, 5, 6, 7, 10]
  2. 选取第一个元素 1,此时 1 < 8,元素 1 入栈
  3. 递归1:选取第二个元素 1,此时 (1 + 1) < 8,元素 1 入栈
  4. 递归2:选取第三个元素 2,此时 (1 + 1 + 2) < 8,元素 2 入栈
  5. 递归3:选取第四个元素 5,此时 (1 + 1 + 2 + 5) > 8。说明无法找到正确的解,结束此层递归。
  6. 递归2:元素 2 出栈,选取第四个元素 5,此时 (1 + 1 + 5) < 8,元素 5 入栈
  7. 递归3:选取第五个元素 6,此时 (1 + 1 + 5 + 6) > 8。说明无法找到正确的解,结束此层递归。
  8. 递归2:元素 5 出栈,选取第五个元素 6,此时 (1 + 1 + 6) == 8。找到了一组正确的解 [1, 1, 6]
  9. ......,后面的解也是通过相同的方式得到的
  10. 最终可以得到正确的解为:[1, 1, 6], [1, 2, 5], [1, 7], [2, 6]

二、实现

实现代码

实现方式与思路中保持一致

/**
 * 回溯算法(值不可以重复)
 */
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
    List<List<Integer>> ret = new ArrayList<>();
    int len = candidates.length;
    if (len == 0)
        return ret;
    Stack<Integer> path = new Stack<>();
    // 回溯前先将数组排序
    Arrays.sort(candidates);
    dfs(candidates, 0, target, path, ret);
    return ret;
}

/**
 * 回溯的实现
 * @param candidates    候选数组
 * @param begin 开始搜索位置
 * @param target    目标值
 * @param path  路径
 * @param ret   结果集
 */
private void dfs(int[] candidates, int begin, int target, Stack<Integer> path, List<List<Integer>> ret) {

    // target为负值和0时不分支
    if (target < 0)
        return;
    if (target == 0) {
        ret.add(new ArrayList<>(path));
        return;
    }

    // 从begin开始搜索
    int len = candidates.length;
    for (int i=begin; i<len; i++) {
        // 同级节点不能重复
        if (i > begin && candidates[i] == candidates[i-1])
            continue;
        // 路径进栈
        path.push(candidates[i]);
        // 考虑到剪枝,下次搜索起点为i+1
        dfs(candidates, i+1, target - candidates[i], path, ret);

        // 路径出栈
        path.pop();

    }
}

测试代码

public static void main(String[] args) {
    int[] nums = {10,1,2,7,6,1,5};
    new Number40().combinationSum2(nums, 8);
}

结果

image.png

三、总结

感谢看到最后,非常荣幸能够帮助到你~♥