leetcode
leetcode 1 ~ 50
组合总和 II

组合总和 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

代码结果

运行时间: 116 ms, 内存: 14.8 MB


// 题目思路:
// 1. 使用Java Stream API来简化数据处理。
// 2. 由于Java Stream不太适合处理回溯问题,我们仍然需要辅助函数。
// 3. 排序、过滤重复项仍是避免重复组合的关键。
 
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.stream.Collectors;
 
public class Solution {
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        return Arrays.stream(candidates).boxed()
                .sorted()
                .collect(Collectors.toList())
                .stream()
                .flatMap(candidate -> findCombinations(candidate, candidates, target))
                .distinct()
                .collect(Collectors.toList());
    }
 
    private Stream<List<Integer>> findCombinations(int candidate, int[] candidates, int target) {
        List<List<Integer>> result = new ArrayList<>();
        List<Integer> combination = new ArrayList<>();
        combination.add(candidate);
        if (candidate == target) {
            result.add(new ArrayList<>(combination));
        } else if (candidate < target) {
            for (int i = 1; i < candidates.length; i++) {
                if (candidates[i] == candidate) continue;
                int[] subCandidates = Arrays.copyOfRange(candidates, i, candidates.length);
                result.addAll(findCombinations(candidates[i], subCandidates, target - candidate));
            }
        }
        return result.stream();
    }
}

解释

方法:

这个题解使用了回溯搜索的方法来找到所有满足条件的组合。首先将候选数组排序,然后从数组的第一个元素开始,通过递归不断尝试将当前元素加入路径中,并继续搜索剩余元素,直到路径和等于目标值时将当前路径加入结果集。搜索过程中,如果路径和已经大于等于目标值,则可以提前终止搜索。此外,为了避免生成重复的组合,在每一层递归时,如果当前元素与上一个元素相同,则跳过该元素,以确保每个元素只使用一次。

时间复杂度:

O(n * 2^n)

空间复杂度:

O(n * 2^n)

代码细节讲解

🦆
为什么在回溯过程中,当发现当前路径和已经大于目标值时,可以直接终止搜索?
在回溯过程中,如果当前路径的和已经大于目标值,继续添加更多的元素只会使路径和更大,从而无法达到目标值。因此,一旦路径和超过目标值,可以立即停止当前路径的进一步搜索,避免无效的计算,这样做可以提高算法的效率。
🦆
题解中提到为了避免生成重复的组合,在每一层递归时跳过与前一个元素相同的情况。能否详细解释如何通过这种方式确保不会产生重复的组合?
在排序后的数组中,相同的元素会排列在一起。在递归过程中,如果在同一层递归中连续遇到相同的元素,只考虑这些元素的第一个,并跳过后续的相同元素可以避免生成重复的组合。例如,对于数组 [1,1,2],在第一个1被加入路径后,第二个1在同一层递归中就会被跳过,这样就只会生成不包含重复组合的所有可能组合。
🦆
题解中使用了递归来实现回溯,递归深度最大为 n。在实际应用中,对于递归深度这样的限制是否有可能导致栈溢出?如果是,应该如何避免?
是的,递归深度过大有可能导致栈溢出,尤其是在处理大数据量或者递归深度特别深的情况下。为了避免栈溢出,可以考虑使用迭代的方式来代替递归,或者在编程语言支持的情况下增加递归调用栈的大小。还有一种方法是使用尾递归优化,尽管这依赖于编程语言的编译器是否支持尾递归优化。
🦆
题解中提到排序候选数组,这个排序步骤对算法的正确性或效率有何影响?
排序候选数组是为了更方便地处理重复元素和提早终止无效的搜索路径。首先,排序使得相同的元素聚集在一起,便于在递归过程中识别和跳过重复的元素,从而避免生成重复的组合。其次,排序后的数组可以更容易地判断当前路径的和是否已经超过目标值,如果超过则可以立即停止进一步的搜索。这个步骤对于提高算法的效率和减少不必要的计算是非常有帮助的。

相关问题

组合总和

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。 

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

 

示例 1:

输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。

示例 2:

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

示例 3:

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

 

提示:

  • 1 <= candidates.length <= 30
  • 2 <= candidates[i] <= 40
  • candidates 的所有元素 互不相同
  • 1 <= target <= 40