文章目录
类型一: 只能用一次
只能使用一次就是要不要的问题
1.如果数组里面的数字只能用一次,就是要不要的问题。
2.如果数组中包含重复数字,需要先排序,然后使用set才能去除重复组合
3.针对重复数字,可将其存入map中,这样要不要的O(2^n)问题就变成了O(n+1)问题,对于重复数字比较多的情况下,可改善情况。
40 组合总数II(只能用一次+含多个重复元素,dfs+map+set)(要几个0-n)
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
注意:解集不能包含重复的组合。
class Solution {
//dfs:这就是要这个数,不要这个数的问题
//因为compute递归函数中的combination仍然是一个容器,所以要这个数的时候还是需要回退
//数组中存在重复元素,存在1 2 5,2 1 5这种组合,虽然1来自于不同的地方,但是仍然算是重复组合,解决这个问题采用了对candidates排序,这样最终的到是1 2 5、1 2 5,set会认为它们是重复的List,只保留一个。
//第二次修改:
//使用上面的技巧在100个1,target为30的时候会出现超时,问题出在相同值的处理上//相同值的问题:要不要当前节点(O(2^n)),而要几个此节点(O(n+1)),因此将相同值的要不要问题转换为要几个的问题
Set<List<Integer>> res = new HashSet<>();//保存不重复的结果
Map<Integer,Integer> map;
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
List<Integer> nums = new ArrayList<>();
map = new HashMap<>();
for(int candidate:candidates){
if(!map.containsKey(candidate)){
nums.add(candidate);
}
map.put(candidate,map.getOrDefault(candidate,0)+1);
}
compute(nums,0,0,target,new ArrayList<Integer>());
List<List<Integer>> list = new ArrayList<>(res);
return list;
}
//要这个数字与不要这个数字的问题了
//num:当前值,index:当前下标,target:目标值,combination:当前组合
public void compute( List<Integer> nums, int num, int index,int target,List<Integer> combination){
if(num > target) {
return;
}else if(num == target){
for(Integer i : combination){
System.out.print(i+" ");
}
System.out.println("");
res.add(new ArrayList<>(combination));
return;
}
if(index == nums.size()){//走到头
return;
}
int value = nums.get(index);
int freq = map.get(value);//当前值的个数
for(int i=0;i<=freq;i++){//要i个
for(int j = 0;j<i;j++){//向组合中添加i个
combination.add(value);
}
compute(nums,num+value*i,index+1,target,combination);
for(int j = 0;j<i;j++){//删除i个
combination.remove(combination.size()-1);
}
}
}
}
78 子集(只使用一次+不含重复元素,dfs)(要不要)
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
class Solution {
//简单的要不要问题
//而且没有重复元素
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
dfs(nums,0,new ArrayDeque<>());
return res;
}
public void dfs(int[] nums, int index, Deque<Integer> path){
if(index == nums.length){
res.add(new ArrayList<>(path));
return;
}
dfs(nums,index+1,path);//要这个元素
path.offerLast(nums[index]);
dfs(nums,index+1,path);//不要这个元素
path.pollLast();
}
}
90 子集II (只使用一次+含重复元素,dfs+排序)(要不要)
class Solution {
//要这个元素,不要这个元素
//去重使用set
//元素顺序不一样:先对nums排序
Set<List<Integer>> res = new HashSet<>();
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);
dfs(nums,0,new ArrayDeque<>());
List<List<Integer>> list = new ArrayList<>(res);
return list;
}
public void dfs(int[] nums, int index, Deque<Integer> path){
if(index == nums.length){
res.add(new ArrayList<>(path));
return;
}
dfs(nums,index+1,path);
path.offerLast(nums[index]);
dfs(nums,index+1,path);
path.pollLast();
}
}
类型二:所有组合问题
77. 组合(所有组合(包含只使用一次的意思),dfs循环添加后面的元素)
给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
你可以按 任何顺序 返回答案
class Solution {
//不是要不要的问题
//所有组合问题:
//每次可将index之后的元素加入到组合中,需要一个for循环将i添加到index中
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
if(k<0 || k>n) return null;
dfs(n, k, 1, new ArrayList<Integer>());
return res;
}
public void dfs(int n,int k,int index, List<Integer> combination){
if(combination.size() == k){
res.add(new ArrayList(combination));
return;
}
for(int i=index;i<=n;i++){//循环的将每一个元素添加到组合中
combination.add(i);
dfs(n,k,i+1,combination);
combination.remove(combination.size()-1);
}
}
}
另一种写法:要不要的写法
class Solution {
//所有组合问题:
//每次可将index之后的元素加入到组合中,需要一个for循环将i添加到index中
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
if(k<0 || k>n) return null;
//开始的时候index初始赋值为0,dfs(n, k, 0, new ArrayList<Integer>())运行解答错误发现多了[[0,4],[0,3],[0,2],[0,1]],开头为0的组合,修改为1
dfs(n, k, 1, new ArrayList<Integer>());
return res;
}
//n:数组元素,k:目标值,num:当前值,index:数组索引,combination:当前组合数
public void dfs(int n,int k,int index, List<Integer> combination){
if(combination.size() == k){
res.add(new ArrayList(combination));
return;
}
if(index > n ) return;//数组界定
//不要
dfs(n,k,index+1,combination);
//要
combination.add(index);
dfs(n,k,index+1,combination);
combination.remove(combination.size()-1);
// for(int i=index;i<=n;i++){//循环的将每一个元素添加到组合中
// combination.add(i);
// dfs(n,k,i+1,combination);
// combination.remove(combination.size()-1);
// }
}
}
216 组合总和II(只使用一次+所有组合+无重复元素,dfs循环添加后面的元素+剪枝)
也可以转换成要不要的问题,所有组合也是要不要的问题啊,那77可以转换吗
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
说明:
所有数字都是正整数。
解集不能包含重复的组合。
class Solution {
//不含重复元素的要不要问题+剪枝
//额外的判断条件:当前组合中的值与target是否相等,大于target时,实行剪枝操作
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
dfs(k,n,0,1,new ArrayDeque<>());
return res;
}
public void dfs(int k, int n, int num, int index, Deque<Integer> path){//从1-9中选择k个数,相加之和为n,当前值为num,当前要走index
if(num > n){//路径path的长度还小于k,num就已经大于n了,则返回
return;
}
if(path.size() == k){//当前的路径等于k,返回
if(num == n){
res.add(new ArrayList<>(path));
}
return;
}
if(index == 10) return;
//分两种情况:添加、不添加
dfs(k,n,num,index+1,path);//不添加,//这次选择了i,下次应该从i+1开始,所以index=i+1
path.offerLast(index);
dfs(k,n,num+index,index+1,path);//这次选择了i,下次应该从i+1开始,所以index=i+1
path.pollLast() ;
// for(int i = index;i<=9;i++){//循环添加index后面的元素到路径中,分两种情况:添加、不添加
// dfs(k,n,num,i+1,path);//不添加,//这次选择了i,下次应该从i+1开始,所以index=i+1
// path.offerLast(i);
// dfs(k,n,num+i,i+1,path);//这次选择了i,下次应该从i+1开始,所以index=i+1
// path.pollLast();
// }
}
}