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. nginx访问fastdfs文件 报错400 Bad Request

    1.修改vi /etc/fdfs/mod_fastdfs.conf 2.将url_have_group_name = false 改为 url_have_group_name = true 3.重启 ...

  2. .Net Core with 微服务 - Consul 配置中心

    上一次我们介绍了Elastic APM组件.这一次我们继续介绍微服务相关组件配置中心的使用方法.本来打算介绍下携程开源的重型配置中心框架 apollo 但是体系实在是太过于庞大,还是让我爱不起来.因为 ...

  3. java_线程创建的三种方式及区别

    java中关于线程的创建有三种: (1)通过继承Thread类创建线程. (2)通过实现Runnable接口创建线程. (3)通过Callable 和 Future 接口创建线程. * * * * * ...

  4. 合并N个长度为M的有序数组为一个N*M的有序数组

    题目:合并N个有序数组,每个数组的长度为M,合并为N*M的有序数组.时间复杂度要求最低 解法:N个数组进行两两合并,合并后的数组再继续执行合并过程,最后合成N*M的有序数组.可以认为合并这个递归过程发 ...

  5. [转载] 笑话:Developer and product manager

    A man flying in a hot air balloon suddenly realizes he's lost. He reduces height and spots a man dow ...

  6. OpenResty简介

    OpenResty(也称为 ngx_openresty)是一个全功能的 Web 应用服务器.它打包了标准的 Nginx 核心,很多的常用的第三方模块,以及它们的大多数依赖项. 通过揉和众多设计良好的 ...

  7. USB数据线 单独供电

    USB数据线上剪掉两个电源线,只保留两个是数据就无法传数据了.数据线传输数据需要通过芯片来进行数据交换,芯片的工作离不开电源,没有电源,芯片无法工作,当然也就无法传输数据了.电源线特别是负极线,同时还 ...

  8. ES6新增语法(一)——let、const、var的区别

    ES6简介 ES6是ECMAScript 6.0的简称,是javascript语言的下一代标准,已经在2015年6月正式发布上线.目的就是为了统一javascript的语法标准,可以用来开发大型应用程 ...

  9. 第 1 题:HTML 和 HTML5 有什么区别?

    概念 HTML5 将成为 HTML.XHTML 以及 HTML DOM 的新标准 文档类型声明 HTML <!DOCTYPE html PUBLIC "-//W3C//DTD HTML ...

  10. c++中的静态成员

    引言 有时候需要类的一些成员与类本身相关联,而不是与类的每个对象相关联.比如类的所有对象都要共享的变量,这个时候我们就要用到类的静态成员. 声明类的静态成员 声明静态成员的方法是使用static关键字 ...