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

205 篇文章 0 订阅
126 篇文章 0 订阅
本文介绍了如何使用回溯算法解决编程问题,给定两个整数n和k,生成[1,n]中所有可能的k个数的组合。通过递归和列表操作实现,讨论了时间复杂度和空间复杂度。
摘要由CSDN通过智能技术生成

一、题目描述

给定两个整数 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函数。

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

三、具体代码

import java.util.ArrayList;
import java.util.List;

public class Solution {
    public List<List<Integer>> combine(int n, int k) {
        List<List<Integer>> result = new ArrayList<>();
        if (k <= 0 || n < k) {
            return result;
        }
        // 从1开始进行回溯
        backtrack(result, new ArrayList<>(), 1, n, k);
        return result;
    }

    private void backtrack(List<List<Integer>> result, List<Integer> comb, int start, int n, int k) {
        if (comb.size() == k) {
            // 找到一个有效的组合
            result.add(new ArrayList<>(comb));
            return;
        }

        for (int i = start; i <= n; i++) {
            // 添加当前数字到组合中
            comb.add(i);
            // 递归地继续向组合中添加数字
            backtrack(result, comb, i + 1, n, k);
            // 回溯:移除最后一个数字,尝试下一个数字
            comb.remove(comb.size() - 1);
        }
    }
}

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

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<List<Integer>> result, List<Integer> comb)来传递参数。在 Java 中,基本数据类型(如 int)是通过值传递的,而对象(如 List)是通过引用传递的。

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一直学习永不止步

谢谢您的鼓励,我会再接再厉的!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值