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. mybatis级联查询,多对一查询问题

    在使用Mybatis进行多表级联查询时遇到了一个问题:查询结果只有一项,但正确结果是两项.经测试,SQL语句本身没有问题. 在SQL映射文件(XML)中: <!-- 级联查询数据 --> ...

  2. 实现理论上无tps上限的分布式压测(基于Jmeter+InfluxDB+Grafana+Spring Boot)

    JMeter自身带有Master-Slave压测框架,对于并发量不是很高的压力情况下(比如tps低于5000),该方案是可行的,并且使用起来非常方便,只要在配置文件或者命令行工具的参数做一些补充,即可 ...

  3. Redis(十):pub/sub 发布订阅源码解析

    谈到发布订阅模式,相信不会陌生,典型的观察者模式的实现.然而从表面来看,本地实现一个wait/notify通知.register/update调用, 实现一个远程mq服务, 还有本文说的 pub/su ...

  4. Mysql 在线新建或重做主从

    1. 前言 以前给 Mysql 数据库做主从,都是在主服务器停服的情况下做的.但是最近有一个项目,已经上线几天了,数据库也单服务器跑了几天,才确定要给 Mysql 服务器做一个主从架构,简单的一主一从 ...

  5. C#设计模式学习笔记:(13)模板方法模式

    本笔记摘抄自:https://www.cnblogs.com/PatrickLiu/p/7837716.html,记录一下学习过程以备后续查用. 一.引言 今天我们要讲行为型设计模式的第一个模式--模 ...

  6. XPath简介、功能及使用方法

    html = '''<html><head><title>The Dormouse's story</title></head><bo ...

  7. html5之table嵌入form表单布局(务必注意:table标签必须在form表单内部,不能再form表单外部!)

    切记:用table标签来布局form表单元素,table标签必须放在form表单内部,否则可能会出现各种bug 原文地址:https://blog.csdn.net/weixin_43343144/a ...

  8. Django单元测试中Fixtures用法

    在使用单元测试时,有时候需要测试数据库中有数据,这时我们可以使用Django的Fixtures来生成测试数据. 基础配置 在settings.py 中配置如下内容: FIXTURE_DIRS = (' ...

  9. linuxWeb环境安装——小皮面板不错的面板

    安装环境为最新的:CentOS8.1.1911  linux的web环境安装,说白了,弄明白了就不难.为此阅读了多部文献,最先的是linux教程,重理论轻实践:之后,看了鸟哥的私房菜,有2本,每本都8 ...

  10. C# WPF从RIOT API获取数据(RIOT代表作品《英雄联盟》)

    微信公众号:Dotnet9,网站:Dotnet9,问题或建议:请网站留言, 如果对您有所帮助:欢迎赞赏. C# WPF从RIOT API获取数据(RIOT代表作品<英雄联盟>) 阅读导航 ...