• LeetCode题练习与总结:组合-77


    一、题目描述

    给定两个整数 nk,返回范围 [1, n] 中所有可能的 k 个数的组合。

    你可以按 任何顺序 返回答案。

    示例 1:

    输入:n = 4, k = 2
    输出:
    [
      [2,4],
      [3,4],
      [2,3],
      [1,2],
      [1,3],
      [1,4],
    ]

    示例 2:

    输入:n = 1, k = 1
    输出:[[1]]

    提示:

    • 1 <= n <= 20
    • 1 <= k <= n

    二、解题思路

    要解决这个问题,我们可以使用回溯算法。回溯算法是一种通过探索所有可能的候选解来找出所有的解的算法。如果候选解被确认不是一个解(或者至少不是最后一个解),回溯算法会通过在上一步进行一些变化丢弃该解,即回溯并且再次尝试。

    为了生成所有可能的组合,我们可以从数字1开始,依次尝试每一个数字,对于每一个数字,我们都可以选择将其放入当前的组合中,或者不放入。如果放入,我们就继续向组合中添加下一个数字;如果不放入,我们就尝试下一个数字。当我们组合中的数字数量达到k时,我们就找到了一个有效的组合,将其添加到结果列表中。

    具体步骤如下:

    1. 初始化一个结果列表result,用于存储所有有效的组合。

    2. 定义一个回溯函数backtrack,该函数接受以下参数:

    • result:结果列表
    • comb:当前组合
    • start:当前尝试的数字
    • n:数字范围上限
    • k:组合大小

    3. 在backtrack函数中,首先检查当前组合的大小是否等于k:

    • 如果是,说明找到了一个有效的组合,将其添加到结果列表中,并返回。
    • 如果不是,继续执行下一步。

    4. 从start开始,遍历所有可能的数字:

    • 将当前数字添加到当前组合中。
    • 递归调用backtrack函数,尝试下一个数字,即start + 1
    • 在递归调用之后,将最后一个数字从当前组合中移除,以便尝试下一个数字。

    5. 在combine函数中,首先检查k是否小于等于0或n是否小于k:

    • 如果是,返回空的结果列表。
    • 如果不是,从1开始调用backtrack函数。

    这样,我们就可以生成所有的组合,并将它们存储在结果列表中。最后,返回结果列表即可。

    三、具体代码

    1. import java.util.ArrayList;
    2. import java.util.List;
    3. public class Solution {
    4. public List> combine(int n, int k) {
    5. List> result = new ArrayList<>();
    6. if (k <= 0 || n < k) {
    7. return result;
    8. }
    9. // 从1开始进行回溯
    10. backtrack(result, new ArrayList<>(), 1, n, k);
    11. return result;
    12. }
    13. private void backtrack(List> result, List comb, int start, int n, int k) {
    14. if (comb.size() == k) {
    15. // 找到一个有效的组合
    16. result.add(new ArrayList<>(comb));
    17. return;
    18. }
    19. for (int i = start; i <= n; i++) {
    20. // 添加当前数字到组合中
    21. comb.add(i);
    22. // 递归地继续向组合中添加数字
    23. backtrack(result, comb, i + 1, n, k);
    24. // 回溯:移除最后一个数字,尝试下一个数字
    25. comb.remove(comb.size() - 1);
    26. }
    27. }
    28. }

    四、时间复杂度和空间复杂度

    1. 时间复杂度
    • 对于每个数字,我们都有两种选择:包含在组合中或不包含。
    • 对于n个数字,共有2^n种可能的组合,但题目要求我们只考虑大小为k的组合。
    • 因此,我们不会探索所有2^n种可能性,而是根据组合数公式C(n, k)进行操作。
    • 组合数公式C(n, k)表示从n个不同元素中取出k个元素的组合数,计算公式为C(n, k) = n! / (k! * (n-k)!).
    • 因此,时间复杂度是O(C(n, k)),即O(n! / (k! * (n-k)!))。
    2. 空间复杂度
    • 空间复杂度主要取决于递归栈的深度和用于存储组合的列表。
    • 递归栈的深度在最坏情况下是k,即当我们要找到一个大小为k的组合时,递归调用会进行k次。
    • 每个组合需要一个大小为k的列表来存储,因此空间复杂度是O(k)。
    • 综上所述,总的空间复杂度是O(k)。

    五、总结知识点

    1. 回溯算法:这是一种通过探索所有可能的候选解来找出所有的解的算法。如果候选解被确认不是一个解(或者至少不是最后一个解),回溯算法会通过在上一步进行一些变化丢弃该解,即回溯并且再次尝试。

    2. 递归backtrack 函数是一个递归函数,它通过自己调用自己来进行深度优先搜索(DFS)。递归允许函数在执行过程中调用自己,从而可以遍历所有可能的组合。

    3. 列表(ArrayList)result 和 comb 都是 ArrayList 类型的列表,用于存储组合结果和当前的组合。ArrayList 是 Java 中的一个可调整大小的数组实现,提供了对元素的快速随机访问。

    4. 条件语句:代码中使用了条件语句(if)来检查组合的大小是否达到了k,以及输入参数是否有效。

    5. 循环语句for 循环用于遍历所有可能的数字,从 start 到 n

    6. 列表操作add 和 remove 方法用于在组合列表中添加和移除元素。这是对列表进行动态操作的基本方法。

    7. 函数定义和调用:代码中定义了 combine 和 backtrack 两个函数,combine 是公共函数,用于对外提供接口,而 backtrack 是一个私有辅助函数,用于实现回溯逻辑。

    8. 参数传递:函数通过值传递(int n, int k)和引用传递(List> result, List comb)来传递参数。在 Java 中,基本数据类型(如 int)是通过值传递的,而对象(如 List)是通过引用传递的。

    9. 深拷贝和浅拷贝:在将 comb 添加到 result 时,使用了 new ArrayList<>(comb) 来创建 comb 的深拷贝。这是因为 ArrayList 是可变的,如果不进行深拷贝,result 中存储的将是 comb 引用的副本,而不是其值的副本。

    以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。

  • 相关阅读:
    C++设计模式---观察者模式
    vue批量手动上传文件
    【Java基础】方法
    欧拉路径!
    Linux 调试之strace
    ios-关联对象
    JMeter-BeanShell预处理程序和BeanShell后置处理程序的应用
    利用IPV6随时访问家中影音Jellyfin
    【无标题】
    fetch的简单使用
  • 原文地址:https://blog.csdn.net/weixin_62860386/article/details/137249229