回溯问题中组合问题的个人总结

有部分图片节选自代码随想录

问题描述:

思考:如果k为2,可以用两层for。k为3可以用三层for,但是k为50,n为100呢?

Q&A:要解决 n为100,k为50的情况,暴力写法需要嵌套50层for循环,那么回溯法就用递归来解决嵌套层数的问题。递归来做层叠嵌套(可以理解是开k层for循环),每一次的递归中嵌套一个for循环,那么递归就可以用于解决多层嵌套循环的问题了。

用递归来代替需要循环的次数

我的实现:

class Solution {
    LinkedList<List<Integer>> result = new LinkedList<>();

    public List<List<Integer>> combine(int n, int k) {
        LinkedList<Integer> list = new LinkedList<>();
        backtracing(list,1, 1, n, k);
        return result;
    }

    public void backtracing(List<Integer> list,int index, int round, int n, int k) {
        if (round > k) {
            return;
        }
    
        for (int i = index; i <= n; i++) {
            list.add(i);
            if (round == k) {
                LinkedList<Integer> copy = new LinkedList<>();
                for (Integer item : list) {
                    copy.add(item);
                }
                result.add(copy);
            }
            backtracing(list, i + 1, round + 1, n, k);
            list.removeLast();
        }
    }
}

可优化的地方:

1、关于循环次数,不需要传入一个round,可以直接用list的size来判断

2、list可以定位全局变量

3、可以剪枝优化

随想录优化版本:

class Solution {
    List<List<Integer>> result= new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> combine(int n, int k) {
        backtracking(n,k,1);
        return result;
    }

    public 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.removeLast();
        }
    }
}

剪枝:省去不必要的运算

假如n=4, k=4,那么第一层for循环的时候,从元素2开始的遍历都没有意义了。 在第二层for循环,从元素3开始的遍历都没有意义了。

剪枝后的版本:

lass Solution {
    List<List<Integer>> result = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> combine(int n, int k) {
        combineHelper(n, k, 1);
        return result;
    }

    /**
     * 每次从集合中选取元素,可选择的范围随着选择的进行而收缩,调整可选择的范围,就是要靠startIndex
     * @param startIndex 用来记录本层递归的中,集合从哪里开始遍历(集合就是[1,...,n] )。
     */
    private void combineHelper(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);
            combineHelper(n, k, i + 1);
            path.removeLast();
        }
    }
}

题目描述:

仿照上面的思路,得到以下代码:

class Solution {
    private int sum = 0;
    private ArrayList<List<Integer>> result = new ArrayList<>();
    private ArrayList<Integer> path = new ArrayList<>();

    public List<List<Integer>> combinationSum3(int k, int n) {
        backtracing(k, n, 1);
        return result;
    }

    public void backtracing(int k, int n, int startIndex) {
        if (sum == n && path.size() == k) {
            result.add(new ArrayList<>(path));
            return;
        }

        for (int i = startIndex; i <= 9; i++ ) {
            path.add(i);
            sum += i;
            backtracing(k, n, i + 1);
            sum -= i;
            path.removeLast();
        }
    }
}

没有组合数量限制的组合:

限制:数组没有重复元素、同一个数字可以无限使用

难点:同一个数字可以无限使用====⇒解决方案:将sum>target作为限制个数的隐形条件

与39的区别:数组candidates元素是存在重复的。在每个组合里每个元素只能用一次

难点:因为这题数组candidates元素是存在重复的,所以实例1中排序完:[1,1,2,5,6,7,10]

如果不加筛选,最后结果会出现[[1,1,6],[1,2,5],[1,7],[1,2,5],[1,7],[2,6]]这样重复的元素

我的实现:

class Solution {
    ArrayList<List<Integer>> result = new ArrayList<>();
    ArrayList<Integer> path = new ArrayList<>();
    int sum = 0;

    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        backtracing(0, candidates, target);
        //HashSet<List<Integer>> set=new HashSet<>(result);
        //return new ArrayList<List<Integer>>(set);
        return result;
    }

    public void backtracing(int startIndex, int[] candidates, int target) {
        if (sum == target) {
            result.add(new ArrayList<>(path));
            return;
        }
        if (sum > target) {
            return;
        }

        for (int i = startIndex; i < candidates.length && sum + candidates[i] <= target; i++) {
            if (i != startIndex && candidates[i] == candidates[i - 1]) {
                continue;
            }
            sum += candidates[i];
            path.add(candidates[i]);
            backtracing(i + 1, candidates, target);
            sum -= candidates[i];
            path.removeLast();
        }
    }
}

剪枝:在for循环中添加sum + candidates[i] <= target判定条件

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值