39. 组合总和

知识点:递归;回溯;组合;剪枝

题目描述

给定一个无重复元素的正整数数组 candidates 和一个正整数 target ,找出 candidates 中所有可以使数字和为目标数 target 的唯一组合。

candidates 中的数字可以无限制重复被选取。如果至少一个所选数字数量不同,则两种组合是唯一的。

对于给定的输入,保证和为 target 的唯一组合数少于 150 个。

示例
输入: candidates = [2,3,6,7], target = 7
输出: [[7],[2,2,3]] 输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]] 输入: candidates = [2], target = 1
输出: [] 输入: candidates = [1], target = 1
输出: [[1]] 输入: candidates = [1], target = 2
输出: [[1,1]]

解法一:回溯

回溯算法的模板:

result = []   //结果集
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径) //把已经做出的选择添加到结果集;
return //一般的回溯函数返回值都是空; for 选择 in 选择列表: //其实每个题的不同很大程度上体现在选择列表上,要注意这个列表的更新,
//比如可能是搜索起点和重点,比如可能是已经达到某个条件,比如可能已经选过了不能再选;
做选择 //把新的选择添加到路径里;路径.add(选择)
backtrack(路径, 选择列表) //递归;
撤销选择 //回溯的过程;路径.remove(选择)

核心就是for循环里的递归,在递归之前做选择,在递归之后撤销选择;


对于本题,有两点和77题组合不一样:

  • 此题可以重复选取选过的元素,所以选择列表的搜索起点不用i+1,仍然是i。
  • 此题没有像之前的题明确给出递归的层数,但是给了target,所以如果相加>target,那就证明到头了;

我们换个角度重新画这个图,和77题有点差距,理解的更全面一点。 其实这就是一个横向循环和纵向的递归,横向循环做出不同的选择,纵向在不同的选择基础上做下一步选择。

class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> res = new ArrayList<>();
Stack<Integer> path = new Stack<>();
backtrack(candidates, target, 0, 0, res, path);
return res;
}
private void backtrack(int[] candidates, int target, int sum, int begin, List<List<Integer>> res, Stack<Integer> path){
if(sum > target){
return;
}
if(sum == target){
res.add(new ArrayList<>(path));
return;
}
for(int i = begin; i < candidates.length; i++){
//做选择;
sum += candidates[i];
path.push(candidates[i]);
//递归:开始下一轮选择;
backtrack(candidates, target, sum, i, res, path); //不用+1,可以重复选;
//撤销选择:回溯
sum -= candidates[i];
path.pop();
}
}
}

解法二:剪枝优化

上述程序有优化的空间,我们可以对数组先进行排序,然后如果找到了当前的sum已经等于target或大于target了,那后面的就可以直接跳过了,因为后面的元素更大,肯定更大于target。

class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> res = new ArrayList<>();
Stack<Integer> path = new Stack<>();
Arrays.sort(candidates); //排序
backtrack(candidates, target, 0, 0, res, path);
return res;
}
private void backtrack(int[] candidates, int target, int sum, int begin, List<List<Integer>> res, Stack<Integer> path){
if(sum == target){
res.add(new ArrayList<>(path));
return;
}
for(int i = begin; i < candidates.length && sum + candidates[i] <= target; i++){
//剪枝:如果sum+candidates[i] > target就结束;
//做选择;
sum += candidates[i];
path.push(candidates[i]);
//递归:开始下一轮选择;
backtrack(candidates, target, sum, i, res, path); //不用+1,可以重复选;
//撤销选择:回溯
sum -= candidates[i];
path.pop();
}
}
}

体会

  • 要能够把这种决策树画出来;
  • 在求和问题中,排序之后加上剪枝是很常见的操作,能够舍弃无关的操作;

相关链接

回溯算法入门级介绍!

组合问题

【LeetCode】39. 组合总和的更多相关文章

  1. Java实现 LeetCode 39 组合总和

    39. 组合总和 给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合. candidates 中的数字 ...

  2. [LeetCode] 39. 组合总和

    题目链接 : https://leetcode-cn.com/problems/combination-sum/ 题目描述: 给定一个无重复元素的数组 candidates 和一个目标数 target ...

  3. [leetcode] 39. 组合总和(Java)(dfs、递归、回溯)

    39. 组合总和 直接暴力思路,用dfs+回溯枚举所有可能组合情况.难点在于每个数可取无数次. 我的枚举思路是: 外层枚举答案数组的长度,即枚举解中的数字个数,从1个开始,到target/ min(c ...

  4. leetcode 39 组合总和 JAVA

    题目: 给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合. candidates 中的数字可以无限制 ...

  5. LeetCode 39. 组合总和(Combination Sum)

    题目描述 给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合. candidates 中的数字可以无限 ...

  6. leetcode 39. 组合总和(python)

    给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合. candidates 中的数字可以无限制重复被选 ...

  7. LeetCode——39. 组合总和

    给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合. candidates 中的数字可以无限制重复被选 ...

  8. Java实现 LeetCode 40 组合总和 II(二)

    40. 组合总和 II 给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合. candidates 中的每个数字在 ...

  9. LeetCode 中级 - 组合总和II(105)

    给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合. candidates 中的每个数字在每个组合中只能使用一次. ...

随机推荐

  1. 10、mysql增量恢复实战

    10.1.实验数据: mysql> select * from test; +----+------+------+ | id | name | age | +----+------+----- ...

  2. SQL 小知识笔记

    1.自动生成序列号 select row_number() over(order by field1) as row_number,* from t_table

  3. Quartz:Quartz定时代码实现

    1.添加pom.xml <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId> ...

  4. logback学习与配置使用

    Logback介绍 Logback 分为三个模块:Core.Classic 和 Access.Core模块是其他两个模块的基础. Classic模块扩展了core模块. Classic模块相当于log ...

  5. MySQL 插入中文后,显示为空白

    https://blog.csdn.net/sun_hj_95/article/details/79488583 在MySQL中插入中文后,显示如下: 解决方案: 在my.ini (在MySQL的安装 ...

  6. 关于easyswoole实现websocket聊天室的步骤解析

    在去年,我们公司内部实现了一个聊天室系统,实现了一个即时在线聊天室功能,可以进行群组,私聊,发图片,文字,语音等功能,那么,这个聊天室是怎么实现的呢?后端又是怎么实现的呢? 后端框架 在后端框架上,我 ...

  7. buu yxx

    一.这题是南邮的题,异性相吸改编过来的,不过那题有给提示,这题没有233 不过做法确是一样的,winhex打开 直接异或,做法是一样的,直接对应的异或就可以了 a="lovelovelove ...

  8. 文末送书四本 | 这篇Java反射机制太经典!不看后悔!

    先看再点赞,给自己一点思考的时间,如果对自己有帮助,微信搜索[程序职场]关注这个执着的职场程序员. 价值:Java技能,面试经验指导,简历优化,职场规划指导,技能提升方法,讲不完的职场故事,个人成长经 ...

  9. 关于vector.size()的一些常见错误总结

    1. 问题引入 通过查看[https://www.cplusplus.com/reference/vector/vector/] 的vector.size()说明,即 member type defi ...

  10. pxe+kickstart部署多个版本的Linux操作系统(下)---实践篇

        我们在企业运维环境中,难免会遇到使用多个Linux操作系统的情况,如果每天都需要安装不同版本的Linux系统的话,那么使用Kickstart只能安装一种版本的Linux系统的方法则显得有些捉襟 ...