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. 第2章 Java并行程序基础(三)

    2.8 程序中的幽灵:隐蔽的错误 2.8.1 无提示的错误案例 以求两个整数的平均值为例.请看下面代码: int v1 = 1073741827; int v2 = 1431655768; Syste ...

  2. ArtiPub:一款开源的一文多发平台

    文章来自我的博客:https://blog.ljyngup.com/archives/705.html/ 看到感觉挺有意思的,有空找个空闲的VPS搭建一下. 转自官方Github仓库 ArtiPub ...

  3. 四步搞定Zabbix 日志文件监控

    Zabbix 日志文件监控 一.给运行Zabbix agent的用户授予要监控日志的读取权限. 1. 執行下面的命令,追加app的可讀權限: setfacl -m u:app:r-- /var/log ...

  4. ZYNQ入门实例——三种GPIO应用、中断系统及软硬件交叉触发调试

    一.前言 Xlinx的ZYNQ系列SOC集成了APU.各种专用外设资源和传统的FPGA逻辑,为ARM+FPGA的应用提供助力,降低功耗和硬件设计难度的同时极大提高两者间传输的带宽.之前在研究生课题中使 ...

  5. CVE-2020-0618 SQL 远程代码执行

    CVE-2020-0618 SQL Server远程代码执行 1.简介 SQL Server Reporting Services(SSRS)提供了一组本地工具和服务,用于创建,部署和管理移动报告和分 ...

  6. zabbix-agent TIME_WAIT 过多(转)

    一.系统环境 操作系统: Centos 6.4 64bit zabbix-agent 版本: Zabbix agent v2.2.7 (revision 50148) (24 October 2014 ...

  7. Springboot feign 传递request信息

    基础实现 requestInterceptor 实现类中添加信息 public class NativeFeignConf { @Bean public RequestInterceptor getR ...

  8. redis教程-基础数据结构

    需要整套redis缓存高可用集群教学视频的加qq:1324981084,本套视频从安装到集群的搭建和源码的解析,从零基础讲解. 一.Redis 有 5 种基础数据结构,分别为:string (字符串) ...

  9. iptables详解(8)iptables自定义链

    自定义链存在的意义:对链进行分类 target可能是一个“动作“也可能是一个”自定义链” 1.新增自定义链. root@ubuntu:~# iptables -t filter -N IN_WEB#结 ...

  10. Mac Docker Desktop "Mounts denied: EOF."解决方法

    环境 系统: Mac OS Catalina Docker Desktop: 问题描述 在Mac环境下创建容器时用"-v"参数挂载目录出现"docker: Error r ...