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. postman之存储测试结果

    前言 在Jmeter的随笔中,我跟大家讲过利用Jmeter工具存储测试结果,那么,postman工具要该如何存储测试结果呢?下面一起来学习吧! 一:添加一个登录请求,填入接口参数点击send 二:点击 ...

  2. [redis读书笔记] 第一部分 数据结构与对象 字典

    三 字典 字典是Hash对象的底层实现,比如用HSET创建一个HASH的对象,底层可能就是用一个字典实现的键值对. 字典的实现主要设计下面三个结构: /* * 哈希表节点 */ typedef str ...

  3. 使用gRPC-Web从浏览器调用.NET gRPC服务

    我很高兴宣布通过.NET对gRPC-Web进行实验性支持.gRPC-Web允许从基于浏览器的应用程序(例如JavaScript SPA或Blazor WebAssembly应用程序)调用gRPC. . ...

  4. golang-练习ATM --面向对象实现

    package utils import ( "fmt" "strings" ) type StructAtm struct { action int loop ...

  5. macos常用命令备查

    常用命令 open . : 命令行打开文件夹 文件编辑 ps: 从一般模式进编辑模式,只需按i.I.a.A.o.O.r和R中某个键即可.当进入编辑模式时,在屏幕尾部会显示INSERT或REPLACE字 ...

  6. js将已有数组重新分组(将数组每10项分成一组)

    项目中碰到的一个小需求:分页请求数据,一次请求60条,需要将后台返回的数组每10条分成一组渲染一个表格(表格使用的是ant-design-vue的table) 实现逻辑: var chunk = 10 ...

  7. Blazui 常见问题:我更新了数据,为什么界面没刷新?

    首发于:http://www.blazor.group:8000/topic/reply?tpid=9 开门见山,不介绍,不废话 建议食用本文前先食用 https://www.cnblogs.com/ ...

  8. go每日新闻--2020-02-19

    gocn_news_2020-02-19 1.使用 t.Cleanup 做测试收尾 https://ieftimov.com/post/testing-in-go-clean-tests-using- ...

  9. nodejs下载网页所有图片

    前言 昨天一番发了一篇批量下载手机壁纸的文章,分享了抓取到的美图给小伙伴,然后一番就美美的去碎觉了. 早上起来看到有小伙伴在日更群里说有没有狗哥的?憨憨的一番以为就是狗的图片,于是就发了几张昨天抓取的 ...

  10. SAP S4HANA如何取到采购订单ITEM里的'条件'选项卡里的条件类型值?

    SAP S4HANA如何取到采购订单ITEM里的'条件'选项卡里的条件类型值? 最近在准备一个采购订单行项目的增强的function spec.其中有一段逻辑是取到采购订单行项目条件里某个指定的条件类 ...