组合总和 II
力扣原题链接
问题描述
给定一个候选人编号的集合 candidates 和一个目标数 target,找出 candidates 中所有可以使数字和为 target 的组合。
每个数字在每个组合中只能使用一次。
注意:解集不能包含重复的组合。
示例
示例 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进行排序,以便后续剪枝操作。 - 回溯搜索: 定义回溯函数 
backtrack,其参数包括当前处理的索引start、当前的目标数target、当前的数字和sum、当前的组合路径path和记录数字是否使用过的数组use。 - 结束条件: 如果当前数字和等于目标数,将当前组合路径加入结果列表,并返回。
 - 选择列表: 获取当前可选的数字集合。
 - 遍历选择: 遍历当前可选的数字集合,对每个数字进行递归搜索。
 - 剪枝操作: 在递归搜索的过程中,如果当前数字等于上一个数字且上一个数字未被使用过,则跳过当前数字,避免选取重复的组合。
 - 做出选择: 将当前数字加入路径,并将当前数字标记为已使用。
 - 递归进入下一层: 递归调用回溯函数,传入新的索引 
i + 1、新的目标数target - candidates[i]、新的数字和sum + candidates[i]、新的组合路径path和更新后的记录数组use。 - 撤销选择: 回溯到上一层时,将当前选择的数字从路径中删除,并将当前数字的使用状态标记为未使用。
 

Java解题
写法一
import java.util.*;
class Solution {
    List<List<Integer>> res = new ArrayList<>();
    
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        List<Integer> path = new ArrayList<>();
        boolean[] use = new boolean[candidates.length];
        Arrays.fill(use, false);
        Arrays.sort(candidates); // 排序数组
        backtrack(candidates, target, 0, 0, path, use);
        return res;
    }
    
    public void backtrack(int[] candidates, int target, int start, int sum, List<Integer> path, boolean[] use) {
        if (sum == target) { // 结束条件
            res.add(new ArrayList<>(path));
            return;
        }
        for (int i = start; i < candidates.length; i++) {
            if (sum > target) break; // 剪枝
            if (i > 0 && candidates[i] == candidates[i - 1] && !use[i - 1]) {
                continue; // 剪枝
            }
            path.add(candidates[i]); // 做出选择
            use[i] = true;
            backtrack(candidates, target, i + 1, sum + candidates[i], path, use); // 递归进入下一层
            use[i] = false; // 撤销选择
            path.remove(path.size() - 1);
        }
    }
}
 
写法二
class Solution {
    List<List<Integer>> combinations = new ArrayList<>();
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates); // 排序数组
        backtrack(candidates, target, 0, new ArrayList<>());
        return combinations;
    }
    private void backtrack(int[] candidates, int target, int start, List<Integer> path) {
        if (target == 0) {
            combinations.add(new ArrayList<>(path));
            return;
        }
        for (int i = start; i < candidates.length; i++) {
            // 剪枝:如果当前数字等于上一个数字且当前索引不等于起始索引,则跳过当前数字
            if (i > start && candidates[i] == candidates[i - 1]) {
                continue;
            }
            if (target - candidates[i] < 0) {
                // 剪枝:如果当前数字大于目标数,则跳过,因为后续数字只会更大
                continue;
            }
            // 做出选择
            path.add(candidates[i]);
            // 递归进入下一层
            backtrack(candidates, target - candidates[i], i + 1, path);
            // 撤销选择
            path.remove(path.size() - 1);
        }
    }
}
                


















