77. 组合

77. 组合

力扣题目链接(opens new window)

给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。

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

思路:回溯(有递归算法就有回溯算法,回溯搜索法也可叫做递归)

看到题目的描述,可用for循环遍历求出集合。比如 n = 4 k = 2
那么定义两个for循环遍历即可,for循环的次数是由k的大小决定
但k的值并不固定,那么for循环次数不确定,也就无法写代码
那么用另一种暴力算法。回溯
可将集合分解为树,并递归。那么如何分解呢?
比如 n = 4 k = 2。

在这里插入图片描述

回溯法也类似递归算法有固定的三要素
1.方法的入参和返回值 回溯法的入参不好确认,可以边写代码便确认。这题入参好确认 int n int k 返回值void
2.终止条件 path.size == 2
3.核心逻辑
定义for循环,循环的次数就是子树的数量。二叉树只有左右两节点,所以只递归两次
for循环每次从startIndex开始遍历,然后用path保存取到的节点i。
回溯操作,撤销本次操作结果

代码如下

//时间复杂度:O(C(n, k) * k),枚举结果总数为C(n, k),每次得到一个结果需要O(k)时间。
//空间复杂度:O(n),最大是n层递归栈。    
static List<Integer> path = new ArrayList<>();
    static List<List<Integer>> result = new ArrayList<>();
    public static void main(String[] args) {
        int n = 4;
        int k = 2;
        combine(4,2);

    }


    public static List<List<Integer>> combine(int n, int k) {
        backTracking(n, k, 1);
        return result;
    }

    public static void backTracking(int n, int k, int startIndex) {
        if (path.size() == k) {// 中止条件
            result.add(new ArrayList<>(path));
            return;
        }

        for (int i = startIndex; i <= n; i++) {
            path.add(i);
            backTracking(n, k, i + 1);
            path.remove(path.size() - 1);
        }

    }

问题

给result结果赋值,忽略引用传递的问题。由于path的值在不断变化,那么会导致存在result中的值也在改动,最终导致result值不对

 if (path.size() == k) {// 中止条件
     result.add(path);
     return;
 }

改正后

if (path.size() == k) {// 中止条件
    result.add(new ArrayList<>(path));
    return;
}

思路:回溯 剪枝优化

回溯法虽然属于暴力算法的一种,但是也可以减枝优化,减少递归的次数
那么什么情况需要减枝呢?举例如下
n=k=4,那么就只有一种情况[1,2,3,4]能符合条件,其余的遍历,那都是浪费时间
https://code-thinking-1253855093.file.myqcloud.com/pics/20210130194335207-20230310134409532.png
如何避免?
减枝的位置,就在本层for循环处,我们可以限制i的上限
如果i后面的元素不足以满足k个元素,那么就不让for循环去搜索
当前已经选择的元素数量: path.size
还需要的元素数量: k-path.size
如果当前一个元素都没有选择,path.size = 0,k = n = 4
那么第一层循环i上限为1 = n-(k-path.size)+1,path.size = 0
第二层循环i上限为2= n-(k-path.size)+1,path.size = 1
第三层循环i上限为3= n-(k-path.size)+1,path.size = 2
第四层循环i上限为4= n-(k-path.size)+1,path.size = 3
为什么n-(k-path.size)+1 要加一?
因为如果i<=n-(k-path.size),在一开始k = n = 4,path.size = 0的时候,就无法加入第一个结点了
需要一个起始区间,所以更正为i<=n-(k-path.size)+1

代码如下

//时间复杂度:O(C(n, k) * k),枚举结果总数为C(n, k),每次得到一个结果需要O(k)时间。
//空间复杂度:O(n),最大是n层递归栈。 
static List<Integer> path = new ArrayList<>();
static List<List<Integer>> result = new ArrayList<>();
public static List<List<Integer>> combine(int n, int k) {
    backTracking(n, k, 1);
    return result;
}

public static void backTracking(int n, int k, int startIndex) {
    if (path.size() == k) {// 中止条件
        result.add(new ArrayList<>(path));
        return;
    }

    for (int i = startIndex; i <= n - k +path.size()+1; i++) {
        path.add(i);
        backTracking(n, k, i + 1);
        path.remove(path.size() - 1);
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值