39题目:

链接:https://leetcode-cn.com/problems/combination-sum/

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

candidates 中的数字可以无限制重复被选取。

说明:

所有数字(包括 target)都是正整数。
解集不能包含重复的组合。

解答:

由于可以无限制选取,所以我们如果选了一次某数字之后,我们还可以再次选择这个数。

注意题目说了无重复元素。所以只要我们按顺序找,并且不选取之前已经放弃的元素,我们最终的解就不会重复。

比如:1,3,2,Target=6。我们第一层遍历时选取了1,然后一定会得到一个1+3+2=6的解。之后我们不选择1,从3开始寻找新的解。除了得到3+3=6之外,我们不能再选取1,因为我们选取1的情况已经在之前考虑过了。

避免重复选取元素的方法有两个:

1.严格按照数组顺序遍历。

代码:

 class Solution {
public:
vector<vector<int>> res;
vector<int> cur;
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
dfs(,target,candidates);
return res;
}
void dfs(int idx,int to_go,vector<int>& nums){
//从idx开始继续选取可能的元素,to_go是距离目标值的差值
if(to_go==){
res.emplace_back(cur);
return;
}
if(idx>=nums.size()){
return;
}
for(int i=idx;i<nums.size();++i){
if(nums[i]<=to_go){
cur.emplace_back(nums[i]);
dfs(i,to_go-nums[i],nums);//注意这里选取i之后,递归的dfs又继续从i开始选取
cur.pop_back();
}
}
}
};

2.先排序,然后按照元素严格递增顺序加入解集,防止出现重复解

由于本题没有重复元素,所以如果我们的每个解的组成数字都是严格递增or递减的,我们也不会求出重复解。那么这种做法要求我们先对数组排序。(虽然其实排好序按元素递增顺序遍历,也是相当于按照数组索引顺序遍历,但毕竟一开始的出发点是不一样的。)

代码:

 class Solution {
public:
vector<vector<int>> res;
vector<int> cur;
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
sort(candidates.begin(),candidates.end());
dfs(,target,candidates);
return res;
}
void dfs(int idx,int to_go,vector<int>& nums){
//从idx开始继续选取可能的元素,to_go是距离目标值的差值
if(to_go==){
res.emplace_back(cur);
return;
}
if(idx>=nums.size()){
return;
}
for(int i=idx;i<nums.size();++i){
if(nums[i]<=to_go){
cur.emplace_back(nums[i]);
dfs(i,to_go-nums[i],nums);//注意这里选取i之后,递归的dfs又继续从i开始选取
cur.pop_back();
}
}
}
};

40题目:

链接:https://leetcode-cn.com/problems/combination-sum-ii/

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用一次。

说明:

所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。

解答:

首先该数组没说无重复,那么就意味着会有重复元素。比如1,2,1,Target=3这种例子,我们只能求出一组(1,2)的解,不能求出(1,2)和(2,1)两组解,因为这是重复解。

另外该数组每个数字只能使用一次,即我们选取了某个数字i,则之后的遍历中都不能选择i了。

所以我们代码需要在前一题的基础上做一些修改。

首先是for循环中递归dfs的时候,需要把下次选取的起始索引从i改为i+1,因为在每个数字只能使用一次的前提下,我们既然选取了i就不能再次选取i了。

即:

dfs(i+,to_go-nums[i],nums);//注意这里选取i之后,递归的dfs从i+1开始选取

如果我们只改动这一句,运行一下我们的代码:

发现会算出重复的解。不难理解,这是由于原数组有重复的元素,而我们的算法没有考虑重复的元素出现。

那么如何去重?有两个方法:

1.暴力。全部解算完之后,对每个解内部排序,放入集合去重,但这样显然不是很好的方法。

2.我们可以举栗子看下为什么算出重复的解:用上面图片的栗子,我们的解出现了两个[1,2,5],原因是先取了第一个1和后面的2,5,它们组成了一个解。之后不取第一个1继续往后遍历。直到选取了2,5和第二个1,这样又构成了一组解。所以看来问题是同样的元素如果分散在数组两侧,我们就可能会算出一样的解。

那么我们先对数组排序,得到1,1,2,5,6,7,10。每一次dfs函数里,我们循环查找能够选取的元素,我们第一次循环中选取了1,那么下次循环中我们就不能再选相邻的第二个1了,即我们要保证每层dfs中我们选取的元素不能有重复的。这也就保证了我们最终得到的每个解内部都是严格非降序的。也就不可能得到重复解了。

如下图,我们画出如下的递归图,可以发现如果第一层中我们选取第二个1,其下面的递归树选取第一个1的递归树子集!所以从这里做剪枝是最好的办法,从根源上杜绝了重复解的出现。

代码:

 class Solution {
public:
vector<vector<int>> res;
vector<int> cur;
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
sort(candidates.begin(),candidates.end());//先排序
dfs(,target,candidates);
return res;
}
void dfs(int idx,int to_go,vector<int>& nums){
//从idx开始继续选取可能的元素,to_go是距离目标值的差值
if(to_go==){
res.emplace_back(cur);
return;
}
if(idx>=nums.size()){
return;
}
for(int i=idx;i<nums.size();++i){
if(i>idx and nums[i]==nums[i-]){//该层里不能选取一样的元素(剪枝)
continue;
}
if(nums[i]<=to_go){
cur.emplace_back(nums[i]);
dfs(i+,to_go-nums[i],nums);//注意这里选取i之后,递归的dfs从i+1开始选取
cur.pop_back();
}
}
}
};

216题目:

链接:https://leetcode-cn.com/problems/combination-sum-iii/

找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。

说明:

所有数字都是正整数。
解集不能包含重复的组合。 
示例 1:

输入: k = 3, n = 7
输出: [[1,2,4]]
示例 2:

输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]

解答:

这题我觉得比上一题简单。首先这道题也要求不存在重复数字,即选取了i之后,就不能再次选取i了。

另外本题的另一个新要求就是解的大小必须恰好是k,比如n=7,k=3的情况,[1,6],[2,5],[7]这样的解都不是有效解,因为长度不满足要求。

我采取的方法是最终放入结果数组时检查当前解的长度是否满足要求。不过还有优化的空间,提前剪枝:

1.如果已经确定之后剩下的元素全放入当前解,长度也不够k,那么就可以提前返回。

2.当前遍历到数字num(num<9),如果当前解加上num到9的元素和都不够n,那么也可以提前返回。

不过这道题数据很小,不必优化。

代码:

 class Solution {
public:
vector<vector<int>> res;
vector<int> cur;
int k,n;
vector<vector<int>> combinationSum3(int k,int n) {
this->k=k,this->n=n;
dfs(,n);
return res;
}
void dfs(int idx,int to_go){
//从idx开始继续选取可能的元素,to_go是当前元素和距离n的差值
if(to_go== and cur.size()==k){
res.emplace_back(cur);
return;
}
if(cur.size()>=k){//最多k个数的限制
return;
}
for(int i=idx;i<;++i){
if(i<=to_go){
cur.emplace_back(i);
dfs(i+,to_go-i);//注意这里选取i之后,递归的dfs从i+1开始选取
cur.pop_back();
}
}
}
};

377题目:

链接:https://leetcode-cn.com/problems/combination-sum-iv/

给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。

示例:

nums = [1, 2, 3]
target = 4 所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1) 请注意,顺序不同的序列被视作不同的组合。 因此输出为 7。

进阶:
如果给定的数组中含有负数会怎么样?
问题会产生什么变化?
我们需要在题目中添加什么限制来允许负数的出现?

解答:

首先按照前面的题目照猫画虎写出dfs:

 class Solution {
public:
int res=;
int cur=;
int combinationSum4(vector<int>& nums, int target) {
sort(nums.begin(),nums.end());
dfs(target,nums);
return res;
}
void dfs(int to_go,vector<int>& nums){
//to_go是距离目标值的差值
if(to_go==){
res+=;
return;
}
for(int i=;i<nums.size() and nums[i]<=to_go;++i){
dfs(to_go-nums[i],nums);//注意这里选取i之后,递归的dfs又继续从i开始选取
}
}
};

然后英勇的超时了

看来这题需要动态规划,dfs无法AC。

DP思路其实很简单,对于[1,2,3]的数组,target=4。

那么我们可以把4看成不同加法的结果:

1+3,其中3的所有可能组合数我们已知;

2+2,其中2的可能组合数我们已知;

3+1;

可以用自顶向下带备忘录的方法,不过我一般习惯使用自底向上求解。

简单递推一下上面的例子:

1:只能1自己,所以dp[1]=1。

2:可以分为1+1,dp[1]=1,所有dp[2]+=1。另外2自己也可以,所以dp[2]+=1,等于2。

3:分为1+2,其中dp[2]=2,所以dp[3]+=2=2;分为2+1,其中dp[1]=1,dp[3]+=1,等于3;3自己也可以,加一,所以最终dp[3]=4。

4:分为1+3,dp[4]+4=4;2+2,dp[4]+2=6;3+1,dp[4]+1=7;最终dp[4]就等于7。

代码:

这题C++的整数溢出比较烦,而可以注意到最终题目返回的却是int。这说明如果某个dp[i]值我们算的超过了INT_MAX,那这个dp[i]最终一定用不上。所以我们简单的跳过不管就好了

 class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
if(nums.empty()){return ;}
vector<int> dp(target+,);//dp[i]:元素和为i的可能组合个数
sort(nums.begin(),nums.end());
int min_val=nums[];
for(int cur=min_val;cur<=target;++cur){
int i=;
for(;i<nums.size() and nums[i]<cur and INT_MAX-dp[cur]>=dp[cur-nums[i]];++i){
//如果超过了INT_MAX就不计算了,反正最后计算dp[target]肯定用不到
dp[cur]+=dp[cur-nums[i]];
}
if(dp[cur]!=INT_MAX and i<nums.size() and nums[i]==cur){//cur自己单独也是一个解
dp[cur]+=;
}
}
return dp[target];
}
};

完结撒花

leetcode四道组合总和问题总结(39+40+216+377)的更多相关文章

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

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

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

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

  3. 【LeetCode】组合总和

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

  4. Leetcode 39 40 216 Combination Sum I II III

    Combination Sum Given a set of candidate numbers (C) and a target number (T), find all unique combin ...

  5. Leetcode之回溯法专题-40. 组合总和 II(Combination Sum II)

    Leetcode之回溯法专题-40. 组合总和 II(Combination Sum II) 给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使 ...

  6. [LeetCode] 39. 组合总和

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

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

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

  8. Java实现 LeetCode 39 组合总和

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

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

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

随机推荐

  1. Go语言实现:【剑指offer】构建乘积数组

    该题目来源于牛客网<剑指offer>专题. 给定一个数组A[0,1,-,n-1],请构建一个数组B[0,1,-,n-1],其中B中的元素B[i]=A[0] * A[1] * - * A[i ...

  2. 数据库连接池 —— Druid的简单使用

    Druid不仅是一个数据库连接池,还包含一个ProxyDriver.一系列内置的JDBC组件库.一个SQL Parser.支持所有JDBC兼容的数据库,包括Oracle.MySql.Derby.Pos ...

  3. Burpsuite Pro 2020.1最新破解版

    简介 Burp Suite 是用于攻击web 应用程序的集成平台,包含了许多工具.Burp Suite为这些工具设计了许多接口,以加快攻击应用程序的过程.所有工具都共享一个请求,并能处理对应的HTTP ...

  4. 浏览器中的 .Net Core —— Blazor WebAssembly 初体验

    前言 在两年多以前就听闻 Blazor 框架,是 .Net 之父的业余实验性项目,其目的是探索 .Net 与 WebAssembly 的兼容性和应用前景.现在这个项目已经正式成为 Asp.Net Co ...

  5. Ikuai路由安装及简单配置 v1.0

    第一部分:创建虚拟机: 1.点击创建新的虚拟机   2.选择自定义模式创建(选择经典模式会更友好一些),然后点击下一步 3.下图内容不用管,直接点击下一步:   4.这里是选择安装系统路径.在这里我们 ...

  6. MyBatis 与Ibatis 区别

    Ibatis 是 Mybatis 的前身,两者都是优秀的持久层框架. 区别: 1.mybatis 实现接口绑定,不需要具体接口实现类.但是需要在xml文件中 的 namespace 绑定具体的接口. ...

  7. Windwos日志分析

    Windows日志分析工具 查看系统日志方法: 在“开始”菜单上,依次指向“所有程序”.“管理工具”,然后单击“事件查看器” 按 "Window+R",输入 ”eventvwr.m ...

  8. dict的使用

    Python字典是可变类型数据,可以存储任意对象,如字符串,数字,元组,列表等. 字典的创键 字典有键key和值value组成,使用键值对链接:,字典也称为关联数组或哈希表. dict_person ...

  9. IoU-aware Single-stage Object Detector for Accurate Localization

    网络的结构如下: 采用FPN结构,Backbone是RetinalNet,分成了P3~P7共5个Layer,分别训练不同尺寸的Box.每个Layer对应的Head有2个分支,包括一个单独的分支用来预测 ...

  10. .net 用反射获取当前运行的程序集中的所有Controller与Action

    public string InitPermission() { // 通过反射加载程序集 // var baseType = Assembly.LoadFile(@"G:\testproj ...