leetcode
leetcode 3001 ~ 3050
组合

组合

难度:

标签:

题目描述

English description is not available for the problem. Please switch to Chinese.

代码结果

运行时间: 28 ms, 内存: 17.4 MB


/*
 * 思路:虽然Java Stream不是最适合回溯算法的工具,但我们可以使用组合的方式来生成所有可能的组合。
 * 这里我们使用一个辅助函数来生成组合并转换为流。
 */
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class SolutionStream {
    public List<List<Integer>> combine(int n, int k) {
        return combineHelper(IntStream.rangeClosed(1, n).boxed().collect(Collectors.toList()), k);
    }

    private List<List<Integer>> combineHelper(List<Integer> nums, int k) {
        if (k == 0) {
            List<List<Integer>> result = new ArrayList<>();
            result.add(new ArrayList<>());
            return result;
        }
        if (nums.isEmpty()) return new ArrayList<>();

        int head = nums.get(0);
        List<Integer> rest = nums.subList(1, nums.size());

        List<List<Integer>> excludingHead = combineHelper(rest, k);
        List<List<Integer>> includingHead = combineHelper(rest, k - 1).stream()
                .peek(list -> list.add(0, head))
                .collect(Collectors.toList());

        List<List<Integer>> result = new ArrayList<>(excludingHead);
        result.addAll(includingHead);
        return result;
    }
}

解释

方法:

这个解决方案使用了回溯法来找出所有可能的k个数的组合。首先,定义一个递归函数fun,它从1到n逐个尝试添加数字到当前组合ans中。如果当前组合的长度到达k,就将其复制后添加到结果列表count中。如果组合未完成,函数继续递归尝试当前数字i的下一个数字i+1。此外,函数在尝试每个数字后,都会将其从组合中移除(回溯),然后尝试下一个数字。此解法还包含一个剪枝的优化:如果当前可选的数字数量小于所需的数量,就提前终止递归。

时间复杂度:

O(2^n)

空间复杂度:

O(n + k)

代码细节讲解

🦆
在递归函数`fun`中,你提到了剪枝的优化,能详细解释一下这种剪枝是如何工作的,以及它为什么能提高效率吗?
在递归函数`fun`中的剪枝操作是通过判断剩余可选数字的数量是否足够完成当前组合来实现的。具体来说,如果当前组合`ans`已经包含了一些数字,那么还需要`k - len(ans)`个数字来完成一个有效的组合。如果从当前数字`i`开始到`n`的数字总数(即`n - i + 1`)小于所需的数字数量(`k - len(ans)`),那么即使继续递归,也无法构成一个有效的组合。因此,此时可以提前终止递归,避免无效的计算和递归调用,从而提高算法的效率。
🦆
递归函数`fun`中有两个递归调用的地方,一个是在添加了当前数字到组合`ans`后,另一个是在`ans`弹出当前数字后,这个过程如何确保不会重复生成相同的组合?
在递归函数`fun`中,第一个递归调用是在将当前数字`i`添加到组合`ans`之后执行的,这意味着正在探索包含数字`i`的所有可能组合。完成这部分递归后,当前数字`i`从组合中被移除(回溯),然后进行第二个递归调用,这次递归不再考虑数字`i`,而是从数字`i+1`开始尝试。这种方法每次递归都是基于不同的起始数字,确保了组合中的数字是按照递增顺序排列,避免了生成重复的组合。例如,组合[1, 2]在回溯后不会再生成[2, 1],因为我们总是从当前数字的下一个开始递归。
🦆
你提到递归的最大深度为`n`,这是怎么确定的?为什么不是`k`,即组合的大小?
递归的最大深度实际上取决于递归函数调用链中最深的那一层,这通常与数字的范围`n`有关,而不单是组合的大小`k`。在这个特定的问题中,尽管我们是在构建大小为`k`的组合,递归函数`fun`却是从1到`n`逐个尝试每个数字,可能会递归到`n`层深,尤其是在极端情况下,比如当`k`接近`n`时。因此,递归的最大深度是`n`而不仅仅是`k`。

相关问题