数组取元素搞排列:递归、回溯、剪枝
想象一棵树,树中不同层次、不同节点即代表了当前选择的不同状态。
dfs遍历该树,根据题目条件进行剪枝回溯,找出所有满足结果即可。
取全部元素:使用标记数组,每次都需要判断每个元素是否可用;
取部分元素:可按照顺序(数组下标)取满足条件的一部分数据,判断是否可用;
数组相关解集无重复问题:一般需要首先对数组进行排序,遍历数组时考虑nums[i]=nums[i+1]
这样某一区间元素相等的情况,大多都只需要对第一个或最后一个元素进行处理,而其他的跳过。
结果集无重复:想象树结构,相同元素,相同层次最多只能取一次,而不同层次则对重复性无影响。
在取第i个位置元素时(即树的第i层),相同元素只能取一次。如[1,[2,2],[1]]中第2层只能取一次2,而[1,[2,1],2]可以取
39. 组合总和
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
思路:
**无重复元素,无限制选取,结果不能重复**: candidates[i] 取? 不取? 取几次?
回溯枚举所有可能情况(排列),中间剪枝优化
public void getList(int[] candidates,int pos,int target,List<Integer> now,List<List<Integer>> ll){
if(target==0){
ll.add(new ArrayList<Integer>(now));
return ;
}
for(int i=0;i<=pos&&candidates[i]<=target;i++){
now.add(candidates[i]);
getList(candidates,i,target-candidates[i],now,ll);
now.remove(now.size()-1);
}
}
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> ll=new ArrayList<List<Integer>>();
Arrays.sort(candidates);
getList(candidates,candidates.length-1,target,new ArrayList<Integer>(),ll);
return ll;
}
40. 组合总和 II
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
说明:
所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。
输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[[1, 7], [1, 2, 5], [2, 6], [1, 1, 6]]
思路: candidates **有重复元素,不能重复选取,结果不能重复**
(一) candidates中包含重复元素,而又要保证最终解集无重复:
考虑不能重复,每个数字是有限的,排序,只保留唯一元素并记录每个元素的出现次数。
对每个可能结果,记录每个元素已出现次数后面尝试时值可以相等,但是不能超过出现次数
== 有点完全背包思想?
public void getList(int[] nums,int[] cnts,int[] vis,int pos,int target,List<Integer> now,List<List<Integer>> ll){
if(target==0){
ll.add(new ArrayList<Integer>(now));
return ;
}
for(int i=0;i<=pos&&nums[i]<=target&&vis[i]<cnts[i];i++){
now.add(nums[i]);
vis[i]+=1;
getList(nums,cnts,vis,i,target-nums[i],now,ll);
vis[i]-=1;
now.remove(now.size()-1);
}
}
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
List<List<Integer>> ll=new ArrayList<List<Integer>>();
Arrays.sort(candidates);
int nums[]=new int[candidates.length];
int cnts[]=new int[candidates.length];
int vis[]=new int[candidates.length];
int len=0;
for(int i=0;i<candidates.length;i++){
if(i==0){
nums[len]=candidates[i];
cnts[len++]+=1;
}else{
if(candidates[i]==nums[len-1]){
cnts[len-1]+=1;
}else{
nums[len]=candidates[i];
cnts[len++]+=1;
}
}
}
getList(nums,cnts,vis,len-1,target,new ArrayList<Integer>(),ll);
return ll;
}
(二) 结合树结构进行剪枝
public void getList(int[] candidates,int pos,int target,List<Integer> now,List<List<Integer>> ll){
if(target==0){
ll.add(new ArrayList<Integer>(now));
return ;
}
for(int i=0;i<=pos&&candidates[i]<=target;i++){
if(i<pos&&candidates[i]==candidates[i+1]){
continue;
}
now.add(candidates[i]);
getList(candidates,i-1,target-candidates[i],now,ll);
now.remove(now.size()-1);
}
}
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
List<List<Integer>> ll=new ArrayList<List<Integer>>();
Arrays.sort(candidates);
getList(candidates,candidates.length-1,target,new ArrayList<Integer>(),ll);
return ll;
}
46. 全排列
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
输入: [1,2,3]
输出:
[[1,2,3],[1,3,2], [2,1,3],[2,3,1],[3,1,2],[3,2,1]]
思路: 数组***无重复元素,结果不能重复,要用到所有元素***:递归+回溯+剪枝, 使用标记数组记录每个方案中每个元素是否使用
public void dfs(int[] nums,int total,int cnt,int[] vis,List<List<Integer>> ll,List<Integer> l){
if(cnt==total){
ll.add(new ArrayList<>(l));
return;
}
for(int i=0;i<total;i++){
if(vis[i]!=0)
continue;
vis[i]=1;
l.add(nums[i]);
dfs(nums,total,cnt+1,vis,ll,l);
l.remove(l.size()-1);
vis[i]=0;
}
}
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> ll=new ArrayList<List<Integer>>();
int[] vis=new int[nums.length];
dfs(nums,nums.length,0,vis,ll,new ArrayList<>());
return ll;
}
47. 全排列 II
给定一个可包含重复数字的序列,返回所有不重复的全排列。
输入: [1,1,2]
输出:
[[1,1,2], [1,2,1], [2,1,1]]
思路:
***有重复元素,结果不能重复,必须用到全部元素***:相较于上一个问题需要考虑排列重复问题,由前面的剪枝技巧,设置每一层相同元素只使用第一个或者最后一个即可。
public void dfs(int[] nums,int total,int cnt,int[] vis,List<List<Integer>> ll,List<Integer> l){
if(cnt==total){
ll.add(new ArrayList<>(l));
return;
}
for(int i=0;i<total;i++){
if((vis[i]!=0)||(i<total-1&&vis[i+1]==0&&nums[i]==nums[i+1]))
continue;
vis[i]=1;
l.add(nums[i]);
dfs(nums,total,cnt+1,vis,ll,l);
l.remove(l.size()-1);
vis[i]=0;
}
}
public List<List<Integer>> permuteUnique(int[] nums) {
List<List<Integer>> ll=new ArrayList<List<Integer>>();
Arrays.sort(nums);
int[] vis=new int[nums.length];
dfs(nums,nums.length,0,vis,ll,new ArrayList<>());
return ll;
}