力扣:组合总和 (系列,先看此篇)

题目描述


给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取
说明:所有数字(包括 target)都是正整数且解集不能包含重复的组合
来源 力扣:39 组合总和
参考大佬 liweiwei1494 的题解

输入:candidates = [2,3,6,7], target = 7,
所求解集为:[[7],[2,2,3]]

题目分析


以下给出的是一种树形图的画法。对于组合来说,还可以根据一个数选和不选画树形图,图片来源
在这里插入图片描述

  • 以 target = 7 为 根结点 ,创建一个分支的时 做减法
  • 每一个箭头表示:从父亲结点的数值减去边上的数值,得到孩子结点的数值。边的值就是题目中给出的 candidate 数组的每个元素的值;
  • 减到 0 或者负数的时候停止,即:结点 0 和负数结点成为叶子结点 (终止条件)
  • 所有从根结点到结点 0 的路径(只能从上往下,没有回路)就是题目要找的一个结果。

AC代码1:

class Solution {
    
    List<List<Integer>> result = new ArrayList<>();
    int[] candidates;
    
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        this.candidates = candidates;
        List<Integer> path = new ArrayList<>();
        dfs(path , 0 , candidates.length , target);
        return result;
    }

    public void dfs(List<Integer> path , int start , int n , int target){
        if(target < 0) return ;
        if(target == 0){
            result.add(new ArrayList<>(path));
            return ;
        }
        for(int i = start ;i < n;i ++){
            path.add(candidates[i]);
            // 注意:由于每一个元素可以重复使用,下一轮搜索的起点依然是 i,这里非常容易弄错
            // 如果是不重复的,则下一轮搜索的起点是i + 1 
            dfs(path , i , n , target - candidates[i]);
            path.remove(path.size() - 1);
        }
    }
}

剪枝提速

  • 根据上面画树形图的经验,如果 target 减去一个数得到负数,那么减去一个更大的树依然是负数,同样搜索不到结果。基于这个想法,我们可以对输入数组进行排序,添加相关逻辑达到进一步剪枝的目的;
  • 排序是为了提高搜索速度,对于解决这个问题来说非必要。但是搜索问题一般复杂度较高,能剪枝就尽量剪枝。实际工作中如果遇到两种方案拿捏不准的情况,都试一下。

AC代码2:

class Solution {
    
    List<List<Integer>> result = new ArrayList<>();
    int[] candidates;
    
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        this.candidates = candidates;
        List<Integer> path = new ArrayList<>();
        //排序是剪枝的前提
        Arrays.sort(candidates);
        dfs(path , 0 , candidates.length , target);
        return result;
    }

    public void dfs(List<Integer> path , int start , int n , int target){
        if(target == 0){
            result.add(new ArrayList<>(path));
            return ;
        }
        for(int i = start ;i < n;i ++){
            // 重点理解这里的剪枝,前提是候选数组已经有序
            if(target - candidates[i] < 0) break;

            path.add(candidates[i]);
            // 注意:由于每一个元素可以重复使用,下一轮搜索的起点依然是 i,这里非常容易弄错
            // 如果是不重复的,则下一轮搜索的起点是i + 1 ,在笔记中给出
            dfs(path , i , n , target - candidates[i]);
            path.remove(path.size() - 1);
        }
    }
}

参考大佬 liweiwei1494 的题解

什么时候使用 used 数组,什么时候使用 begin 变量

  • 排列问题,讲究顺序(即 [2, 2, 3] 与 [2, 3, 2] 视为不同列表时),需要记录哪些数字已经使用过,此时用 used 数组;
  • 组合问题,不讲究顺序(即 [2, 2, 3] 与 [2, 3, 2] 视为相同列表时),需要按照某种顺序搜索,此时使用 begin 变量。
    注意:具体问题应该具体分析, 理解算法的设计思想 是至关重要的,请不要死记硬背。

相似题目:
力扣:组合总和 II         力扣:组合总和 III        力扣:组合总和 IV

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值