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. 2.QT浏览器控件设置“透明颜色”

    使用样式表或者设置背景颜色,使用 background-color:transparent 但,使用透明的颜色是不可行的: QColor(255,0,0,0)

  2. ps 合并两张图片

    1.ps 打开第一张图片,2.打开另一张图片为图层.3.选中图层,创建蒙版: 4.点击蒙版+按Alt键,打开蒙版:5.打开蒙版后选中渐变工具,途中黑色为不显示区域,(注意渐变模式要改为正常):6.调整 ...

  3. UI自动化学习笔记- Selenium元素定位及元素操作

    一.元素定位 1. 如何进行元素定位? 元素定位就是通过元素的信息或元素层级结构来定位元素的 2.定位工具 浏览器开发者工具 3.元素定位方式 Selenium提供了八种定位元素方式 id name ...

  4. gRPC四种模式、认证和授权实战演示,必赞~~~

    前言 上一篇对gRPC进行简单介绍,并通过示例体验了一下开发过程.接下来说说实际开发常用功能,如:gRPC的四种模式.gRPC集成JWT做认证和授权等. 正文 1. gRPC四种模式服务 以下案例演示 ...

  5. 资源:Kafka消息队列下载路径

    Kafka下载路径 http://kafka.apache.org/downloads.html

  6. 如何Spring Cloud Zuul作为网关的分布式系统中整合Swagger文档在同一个页面上

    本文不涉及技术,只是单纯的一个小技巧. 阅读本文前,你需要对spring-cloud-zuul.spring-cloud-eureka.以及swagger的配置和使用有所了解. 如果你的系统也是用zu ...

  7. XCTF re-100

    一.无壳并拉入ida64静态调试(注释说的很明白了) 二.confuseKey是个关键函数,进入看看 发现就是将我们所输入的字符串分割,并把顺序调换了,调回来就是我们的flag. 三.flag: 提交 ...

  8. c语言格式字符

    格式说明由"%"和格式字符组成,如%d%f等.它的作用是将输出的数据转换为指定的格式输出.格式说明总是由"%"字符开始的. 格式字符有d,o,x,u,c,s,f ...

  9. 常见内部排序算法对比分析及C++ 实现代码

    内部排序是指在排序期间数据元素全部存放在内存的排序.外部排序是指在排序期间全部元素的个数过多,不能同时存放在内存,必须根据排序过程的要求,不断在内存和外存之间移动的排序.本次主要介绍常见的内部排序算法 ...

  10. Jenkins 进阶篇 - 权限案例实践

    为了演示方便,我这里创建了如下的一些任务,各个任务由包含在对应的目录中树形结构大概如下: jenkins-authority 目录类型任务 ├── dev-project-group 目录类型任务 │ ...