40.组合总和II

121 阅读4分钟

一、

给定一个候选人编号的集合 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

二、

class Solution {
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        List<List<Integer>> res = new ArrayList<>();
        List<Integer> combine = new ArrayList<>();
        Arrays.sort(candidates);
        List<int[]> numList = new ArrayList<>(); //用于存储数组中每个单独数字出现的次数
        for (int candidate : candidates) {
            int size = numList.size();
            if (numList.isEmpty() || numList.get(size - 1)[0] != candidate) {
                numList.add(new int[]{candidate, 1});
            } else {
                ++numList.get(size - 1)[1];
            }
        }
        dfs(res, combine, numList, target, 0);
        return res;
    }

    public void dfs(List<List<Integer>> res, List<Integer> combine, List<int[]> numList, int target, int idx) {
        // 满足条件加入结果集
        if(target == 0){
            res.add(new ArrayList<>(combine));
            return;
        }

        // 不满足条件直接进入下一次循环
        if(idx == numList.size() || numList.get(idx)[0] > target){
            return;
        }

        // 不取当前位置的数字, 直接下一个数字
        dfs(res, combine, numList, target, idx+1);

        int most = Math.min(target / numList.get(idx)[0], numList.get(idx)[1]);
        for (int i = 1; i <= most; i++) {
            combine.add(numList.get(idx)[0]);
            // 取了i次当前位置数字, 每取一次需要去判断一次能不能满足
            dfs(res, combine, numList, target - i*numList.get(idx)[0], idx+1);
        }
        for (int i = 0; i < most; i++) {
            // 最终需要移除掉上面加入的当前位置数字
            combine.remove(combine.size()-1);
        }
    }
} 

这段代码是一个Java类Solution,它包含了一个用于解决组合总和问题的方法combinationSum2以及一个辅助的递归方法dfs。下面我将逐行解释这段代码:

  1. public List<List<Integer>> combinationSum2(int[] candidates, int target) { 这是combinationSum2方法的定义,它接受两个参数,一个整数数组candidates和一个整数target,并返回一个列表,其中包含满足特定条件的组合。

  2. List<List<Integer>> res = new ArrayList<>(); 在这一行,创建了一个名为res的列表,用于存储最终的结果,即满足条件的组合。

  3. List<Integer> combine = new ArrayList<>(); 创建了一个名为combine的列表,用于在递归中存储当前的组合。

  4. Arrays.sort(candidates); 对输入的candidates数组进行排序,以便在后续的组合生成中更容易处理。

  5. List<int[]> numList = new ArrayList<>(); 创建一个名为numList的列表,用于存储数组中每个唯一数字以及它们出现的次数。

  6. 接下来的循环用于填充numList,它遍历candidates数组并记录每个数字的出现次数。

  7. dfs(res, combine, numList, target, 0); 调用递归方法dfs来开始解决组合总和问题,并将结果存储在res中。

  8. public void dfs(List<List<Integer>> res, List<Integer> combine, List<int[]> numList, int target, int idx) { 这是递归方法dfs的定义,它接受多个参数,包括结果列表res、当前组合combine、数字列表numList、目标值target和当前处理的数字索引idx

  9. if (target == 0) { 这个条件检查是否已经找到一个满足条件的组合,如果target等于0,表示已经找到一个合法的组合,将其添加到结果列表中。

  10. if (idx == numList.size() || numList.get(idx)[0] > target) { 这个条件检查是否需要继续递归生成组合。如果idx等于numList的大小或者numList中的当前数字大于target,则没有必要继续递归。

  11. dfs(res, combine, numList, target, idx + 1); 这一行调用dfs方法,跳到下一个数字的位置,即不取当前位置的数字。

  12. int most = Math.min(target / numList.get(idx)[0], numList.get(idx)[1]); 计算可以取当前位置数字的最大次数,以便在循环中生成不同的组合。

  13. 接下来的循环用于生成不同次数的当前位置数字的组合,然后递归进一步处理。这部分代码实际上处理了组合总和问题的核心逻辑。

  14. 最后一个循环用于移除combine中上面添加的当前位置数字,以便继续生成其他组合。

三、

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

以下是针对输入candidates = [10, 1, 2, 7, 6, 1, 5]target = 8的代码运行过程的详细描述:

  1. 首先,将候选数组candidates排序,得到[1, 1, 2, 5, 6, 7, 10],以方便后续处理。

  2. 创建一个名为numList的列表,用于存储每个数字及其出现次数。在这个例子中,numList将包含以下内容:

    cssCopy codenumList = [[1, 2], [2, 1], [5, 1], [6, 1], [7, 1], [10, 1]]
    
  3. 然后,调用dfs方法,开始递归处理组合总和问题。初始调用如下:

    scssCopy codedfs(res, combine, numList, 8, 0)
    
  4. 进入dfs方法,首先检查是否已经找到一个满足条件的组合。此时target等于8,尚未满足条件。

  5. 然后,检查是否需要继续递归生成组合。由于idx等于0,且numList.get(0)[0]等于1,小于等于8,因此需要继续递归。

  6. 进入循环,首先处理取0次1的情况:

    cssCopy codecombine = [], target = 8, idx = 1
    
  7. 接下来处理取1次1的情况:

    cssCopy codecombine = [1], target = 7, idx = 1
    
  8. 继续递归,处理取0次2的情况:

    cssCopy codecombine = [1], target = 7, idx = 2
    
  9. 接下来处理取1次2的情况:

    cssCopy codecombine = [1, 2], target = 5, idx = 2
    
  10. 继续递归,处理取0次5的情况:

cssCopy codecombine = [1, 2], target = 5, idx = 3
  1. 接下来处理取1次5的情况:

    cssCopy codecombine = [1, 2, 5], target = 0, idx = 3

  2. 满足条件,将组合[1, 2, 5]添加到结果列表res中。

  3. 回溯到上一层,移除最后一个数字5,变为:

    cssCopy codecombine = [1, 2], target = 5, idx = 3
    
  4. 然后处理取2次5的情况:

    cssCopy codecombine = [1, 2, 5, 5], target = -5, idx = 3
    
  5. 不满足条件,继续回溯到上一层,移除最后一个数字5。

  6. 回溯到上一层,移除数字2,变为:

    cssCopy codecombine = [1], target = 7, idx = 2
    
  7. 接着处理取0次2的情况:

    cssCopy codecombine = [1], target = 7, idx = 3
    
  8. 继续处理取1次2的情况,以及取0次5和1的情况。

  9. 继续递归的过程会依次处理各种组合情况,直到找到所有满足条件的组合为止。

  10. 最终,res中包含以下组合:

    csharpCopy code[  [1, 1, 6],
      [1, 2, 5],
      [1, 7],
      [2, 6]
    ]
    

这些组合的数字总和等于目标值8。整个过程是通过深度优先搜索(DFS)实现的,递归探索了所有可能的组合情况。