代码随想录:回溯算法2-4

77.组合

题目

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

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

示例 1:

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

代码(非剪枝)

class Solution {

    List<List<Integer>> res = new ArrayList<>(); //存放所有组合
    List<Integer> path = new ArrayList<>();  //存放当前路径组合
    
    public List<List<Integer>> combine(int n, int k) {
        backTracking(n,k,1);
        return res;
    }
    public void backTracking(int n, int k, int index){
        //终止条件,path大小==k
        if(path.size() == k){
            //注意必须要new,不能直接res.add(path),path是引用类型,地址复用
            res.add(new ArrayList<>(path));
            return;
        }
        
        //for循环遍历n个数字,分别从1,2,3,4,开始拿第一个数字,相当于树横着走
        for(int i = index; i <= n; i++){
            path.add(i); //取i放入path
            backTracking(n,k,i+1); //递归[i+1,n],选后面的数字,相当于树往下走
            path.remove(path.size() - 1); //回溯,把i取出
        }
    }
}

代码(剪枝)

class Solution {

    List<List<Integer>> res = new ArrayList<>(); //存放所有组合
    List<Integer> path = new ArrayList<>();  //存放当前路径组合
    
    public List<List<Integer>> combine(int n, int k) {
        backTracking(n,k,1);
        return res;
    }
    public void backTracking(int n, int k, int index){
        //终止条件,path大小==k
        if(path.size() == k){
            //注意必须要new,不能直接res.add(path),path是引用类型,地址复用
            res.add(new ArrayList<>(path));
            return;
        }
        
        //for循环遍历n个数字,分别从1,2,3,4,开始拿第一个数字
        //剪枝条件:n=4,k-4,path为空时,i只要到1就行,从2开始都不可能有四个数字
        for(int i = index; i <= n + 1 - (k - path.size); i++){
            path.add(i); //取i放入path
            backTracking(n,k,i+1); //递归[i+1,n],那后面的数组
            path.remove(path.size() - 1); //回溯,把i取出
        }
    }
}

总结

        其实就是用一个树来模拟我们手动进行组合数字的过程。(假设n=4,k=2)

        A.先考虑for循环,从[1-n]遍历,其实就是先选第一个数字1,path里面放了1,选完第一个数字add之后,执行backTracking(n,k,2),过程如下。

        ①递归[2-n],i=2,选择第二个数字2,path里放了12。选完第二个数字之后,递归[3-n],这时因为path里有2个元素了,直接进入终止条件,把path12加入res然后直接return。return后,程序回到了递归[2-n],且i=2时的第三行代码,这时将path中的2移除。

        ②继续执行递归[2-n],这时i=3,选择第二个数字3,path里放了13。 选完第二个数字之后,递归[4-n],这时因为path里有2个元素了,直接进入终止条件,把path13加入res然后直接return。return后,程序回到了递归[2-n],且i=3时的第三行代码,这时将path中的3移除。

         ③继续执行递归[2-n],这时i=4,选择第二个数字4,path里放了14。 选完第二个数字之后,递归[5-n],这时因为path里有2个元素了,直接进入终止条件,把path14加入res然后直接return。return后,程序回到了递归[2-n],且i=4时的第三行代码,这时将path中的4移除。

        执行完上面的①②③,相当于backTracking(n,k,2)执行完了,程序回到backTracking(n,k,2)的后一行,将path中的1移除。然后执行B的for循环,从[1-n]遍历,这时i=2了,其实就是先选第一个数字2,path里面放了2,选完第一个数字add之后,执行backTracking(n,k,3),继续递归选第二个数字,流程和前面的类似。后面执行C和D的for循环,i=3和i=4,直到for循环,从[1-n]遍历的循环结束。

216.组合总和III

题目

找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:

  • 只使用数字1到9
  • 每个数字 最多使用一次 

返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。

示例 1:

输入: k = 3, n = 7
输出: [[1,2,4]]
解释:
1 + 2 + 4 = 7

代码(非剪枝)

class Solution {

    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    int sum = 0;  //存放当前path的和
    public List<List<Integer>> combinationSum3(int k, int n) {
        backTracking(k,n,1);
        return res;
    }
    public void backTracking(int k,int n,int index){
        //终止条件
        if(sum == n && path.size() == k){
            //注意点:必须要new
            res.add(new ArrayList<>(path));
            return;
        }

        for(int i=index; i <= 9; i++){
            //取出数字i
            path.add(i);
            sum += i;
            //往树的下面递归,[i+1,9],取其他数字
            backTracking(k,n,i+1);
            //数字1回溯弹出
            sum -= i;
            path.remove(path.size()-1);
        }
    }
}

代码(剪枝)

class Solution {

    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    int sum = 0;  //存放当前path的和
    public List<List<Integer>> combinationSum3(int k, int n) {
        backTracking(k,n,1);
        return res;
    }
    public void backTracking(int k,int n,int index){
        //剪枝,如果sum超过n或者元素个数超过k,不用继续往树下面递归了。
        if(sum > n || path.size() > k){
            return;
        }
        //终止条件
        if(sum == n && path.size() == k){
            //注意点:必须要new
            res.add(new ArrayList<>(path));
            return;
        }
        //9 - (k - path.size()用了剪枝,保证肯定有k个元素,直接写9也行
        for(int i=index; i <= 9 - (k - path.size()) + 1 ;i++){
            //取出数字i
            path.add(i);
            sum += i;
            //往树的下面递归,[i+1,9],取其他数字
            backTracking(k,n,i+1);
            //数字1回溯弹出
            sum -= i;
            path.remove(path.size()-1);
        }
    }
}

总结

        和77和类似,在组合数字的同时,题目77只限定的元素的个数k,这一题还限定了元素的和n。for循环用于从[1-9]选择第一个元素,把path和sum更新后,递归调用backTracking(k,n,i+1),树继续向下递归,选择其他的元素。选择完把第一个元素回溯弹出。

        剪枝方面,在77的基础上i <= 9 - (k - path.size()) + 1 ,用于保证for循环中肯定可以拿出k个元素用于组合。如果sum已经超过了n,或者path中的元素个数超过了k,都可以结束递归的过程,作为终止条件,直接return。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

守岁白驹hh

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值