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. android 基础学习笔记1

    1.控件 XML种控件必须带有Layoutwidth 和height 1.textview 常用属性 text,textcolor,textsize Android 种颜色用十六进制数表示,共四种形式 ...

  2. BeautifulSoup入门

    BeautifulSoup库入门 BeautifulSoup库的理解 BeautifulSoup库是解析.遍历.维护”标签树”的功能库 示例代码: from bs4 import BeautifulS ...

  3. [转]java 为什么wait(),notify(),notifyAll()必须在同步方法/代码块中调用?

    在 Java中,所有对象都能够被作为"监视器monitor"——指一个拥有一个独占锁,一个入口队列和一个等待队列的实体entity. 所有对象的非同步 方法都能够在任意时刻被任意线 ...

  4. saltstack集合

    saltstack集合 saltstack(一):   saltstack简介 saltstack(二):   saltstack安装及配置 saltstack(三):   saltstack远程执行 ...

  5. Angular 从入坑到挖坑 - 组件食用指南

    一.Overview angular 入坑记录的笔记第二篇,介绍组件中的相关概念,以及如何在 angular 中通过使用组件来完成系统功能的实现 对应官方文档地址: 显示数据 模板语法 用户输入 组件 ...

  6. 全文检索框架---Lucene

    一.什么是全文检索 1.数据分类 我们生活中的数据总体分为两种:结构化数据和非结构化数据.   结构化数据:指具有固定格式或有限长度的数据,如数据库,元数据等.   非结构化数据:指不定长或无固定格式 ...

  7. 文本段落缩进text-indent:2em

    中文文字中的段前习惯空两个文字的空白,这个特殊的样式可以用下面代码来实现: p{text-indent:2em;} <p>1922年的春天,一个想要成名名叫尼克卡拉威(托比?马奎尔Tobe ...

  8. Deepin下将Caps映射为Control_L键

    xmodmap -e 'clear Lock' -e 'keycode 0x42 = Control_L'

  9. js能力测评——查找元素的位置

    查找元素的位置 题目描述: 找出元素 item 在给定数组 arr 中的位置 输出描述: 如果数组中存在 item,则返回元素在数组中的位置,否则返回 -1 示例1 输入 [ 1, 2, 3, 4 ] ...

  10. C# NewtonJson Serialize and deserialize

    using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using Sys ...