力扣第四十题——组合总和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]
]

提示:

  • 1 <= candidates.length <= 100
  • 1 <= candidates[i] <= 50
  • 1 <= target <= 30

完整代码

 import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.List;

public 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<>(len);
        dfs(candidates, len, 0, target, path, res);
        return res;
    }

    /**
     * @param candidates 候选数组
     * @param len        冗余变量
     * @param begin      从候选数组的 begin 位置开始搜索
     * @param target     表示剩余,这个值一开始等于 target,基于题目中说明的"所有数字(包括目标数)都是正整数"这个条件
     * @param path       从根结点到叶子结点的路径
     * @param res
     */
    private void dfs(int[] candidates, int len, int begin, int target, Deque<Integer> path, List<List<Integer>> res) {
        if (target == 0) {
            res.add(new ArrayList<>(path));
            return;
        }
        for (int i = begin; i < len; i++) {
            // 大剪枝:减去 candidates[i] 小于 0,减去后面的 candidates[i + 1]、candidates[i + 2] 肯定也小于 0,因此用 break
            if (target - candidates[i] < 0) {
                break;
            }

            // 小剪枝:同一层相同数值的结点,从第 2 个开始,候选数更少,结果一定发生重复,因此跳过,用 continue
            if (i > begin && candidates[i] == candidates[i - 1]) {
                continue;
            }

            path.addLast(candidates[i]);
            // 调试语句 ①
            // System.out.println("递归之前 => " + path + ",剩余 = " + (target - candidates[i]));

            // 因为元素不可以重复使用,这里递归传递下去的是 i + 1 而不是 i
            dfs(candidates, len, i + 1, target - candidates[i], path, res);

            path.removeLast();
            // 调试语句 ②
            // System.out.println("递归之后 => " + path + ",剩余 = " + (target - candidates[i]));
        }
    }

    public static void main(String[] args) {
        int[] candidates = new int[]{10, 1, 2, 7, 6, 1, 5};
        int target = 8;
        Solution solution = new Solution();
        List<List<Integer>> res = solution.combinationSum2(candidates, target);
        System.out.println("输出 => " + res);
    }
}

思路详解

代码详解

public List<List<Integer>> combinationSum2(int[] candidates, int target) {
    // 初始化结果列表
    List<List<Integer>> res = new ArrayList<>();
    // 排序候选数组
    Arrays.sort(candidates);
    // 创建路径栈和当前路径
    Deque<Integer> path = new ArrayDeque<>(candidates.length);
    // 调用递归函数
    dfs(candidates, 0, target, path, res);
    // 返回结果列表
    return res;
}

private void dfs(int[] candidates, int begin, int target, Deque<Integer> path, List<List<Integer>> res) {
    // 如果目标值为0,说明找到了一个有效的组合
    if (target == 0) {
        res.add(new ArrayList<>(path));
        return;
    }
    // 遍历候选数组
    for (int i = begin; i < candidates.length; i++) {
        // 大剪枝:如果当前数字减去目标值小于0,则不可能再通过其他数字凑成目标值
        if (target - candidates[i] < 0) {
            break;
        }
        // 小剪枝:如果当前数字与上一个数字相同,则不可能通过其他数字凑成目标值
        if (i > begin && candidates[i] == candidates[i - 1]) {
            continue;
        }
        // 将当前数字添加到路径中
        path.addLast(candidates[i]);
        // 递归调用,传递下一层的目标值和下一个起始位置
        dfs(candidates, i + 1, target - candidates[i], path, res);
        // 回溯,移除当前数字
        path.removeLast();
    }
}

关键步骤解释

  1. 初始化

    • combinationSum2函数中,首先初始化结果列表res,并对候选数组进行排序。
  2. 递归函数定义

    • dfs函数是递归函数,它接受候选数组、当前起始位置、剩余目标值、当前路径栈和结果列表作为参数。
  3. 递归终止条件

    • 如果剩余目标值等于0,说明找到了一个有效的组合,函数将当前组合保存到结果列表中,并返回。
  4. 剪枝

    • 在递归函数中,使用大剪枝和小剪枝来优化搜索空间。大剪枝用于剪掉不可能达到目标值的路径,小剪枝用于避免重复的结果。
  5. 路径更新

    • 在递归函数中,将当前数字添加到路径栈中,并递归调用自身。
  6. 回溯

    • 在递归函数中,当路径不再有效时,通过移除路径栈中的最后一个元素来实现回溯。
  7. 结果保存

    • 在递归函数中,当找到一个有效的组合时,将当前组合保存到结果列表中。
  8. 返回结果

    • combinationSum2函数中,返回结果列表。

知识点精炼

  1. 回溯算法

    • 使用递归方法dfs来遍历所有可能的组合。
  2. 剪枝技术

    • 在递归函数中应用大剪枝和小剪枝技术,以避免不必要的搜索。
  3. 栈与队列

    • 使用ArrayDeque作为路径栈,高效地管理递归过程中的路径。
  4. 排序数组

    • combinationSum2函数中,对候选数组进行排序,以便在递归过程中应用剪枝。
  5. 状态更新

    • 在递归函数中,使用pathtarget来跟踪当前路径和剩余目标值。
  6. 数组索引操作

    • 通过数组索引来访问和更新候选数组。
  7. 递归调用

    • 在递归函数中,通过begintarget - candidates[i]来控制递归的深度和路径。
  8. 结果保存

    • 在递归函数中,将有效的组合保存到结果列表中。
  9. 调试语句

    • 在递归函数中,添加调试语句来输出当前路径和剩余目标值,以便于调试。
  10. 返回值

    • combinationSum2函数中,返回结果列表。

 

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值