描述:给一个没有重复元素的数组,返回其所有子数组构成的数组
Input:[1,2,3]
Output:[[],[1],[1,2],[1,3],[1,2,3],[2],[2,3],[3]]
解法一:递归,DFS;在一个可能的子数组中,源数组的每个元素要嘛存在,要嘛不存在,两种状态,和二叉树正好吻合,如下图的二叉树(满二叉树),通过深度优先搜索,所有叶子节点的值就是所有可能的子集,所以递归深度到达叶子时就return
代码:
class Solution {
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> result = new ArrayList();
List<Integer> temp = new ArrayList();
generate(0,nums,result,temp);
return result;
}
public void generate(int deepth, int[] nums,
List<List<Integer>> result, List<Integer> temp){
if(deepth==nums.length){
result.add(temp);
return;
}
//源数组第i元素不在子集中
generate(deepth+1, nums, result, new ArrayList<>(temp));
temp.add(nums[deepth]);
//源数组第i元素在子集中
generate(deepth+1, nums, result, new ArrayList<>(temp));
}
}
解法二:递归+循环;记nums[i:]为nums从第i个元素到最后一个元素的部分,nums[1:0]的所有子集结果为tempRes,nums[0:]的所有子集结果=tempRes的每一个元素前面加上nums[0]所构成的结果tempRes0+tempRes
代码:
class Solution {
public List<List<Integer>> subsets(int[] nums) {
return generate(nums,0);
}
public List<List<Integer>> generate(int[] nums, int start){
List<List<Integer>> result = new ArrayList();
if(start == nums.length-1){
List<Integer> list1 = new ArrayList();
List<Integer> list2 = new ArrayList();
list2.add(nums[nums.length-1]);
result.add(list1);
result.add(list2);
return result;
}
List<List<Integer>> temp1 = generate(nums, start+1);
for(List<Integer> l:temp1){
//新复制的数组
List<Integer> tempList = new ArrayList();
for(int i:l){
tempList.add(i);
}
tempList.add(nums[start]);
result.add(tempList);
result.add(l);
}
return result;
}
}
解法三:递归+循环;回溯的思想,也是将生成子集的过程理解为一个二叉树
(1)外层循环逐一往中间集合 temp 中加入元素 nums[i],使这个元素处于存在状态
(2)开始递归,递归中携带加入新元素的 temp,并且下一次循环的起始是 i 元素的下一个,因而递归中更新 i 值为 i + 1
(3)将这个从中间集合 temp 中移除,使该元素处于不存在状态
代码:
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> list = new ArrayList();
Arrays.sort(nums);
backtrack(list, new ArrayList(),nums,0);
return list;
}
public void backtrack(List<List<Integer>> list,List<Integer> tempList, int[] nums,int start){
list.add(new ArrayList<>(tempList));
for(int i=start;i<nums.length;i++){
tempList.add(nums[i]);
backtrack(list, tempList, nums, i+1);
tempList.remove(tempList.size()-1);
}
}
思考 :这里的递归+循环也可以遍历完所有的子集生成情况,这里和第一种解法不同,第一种解法是当递归深度达到叶子节点时才向结果集res中加入temp,这里第三种解法是,在本节点就将本节点处(原数组遍历到第i位)temp的值先加入,然后将子集中有nums[i+1]的所有子集加入res,和子集中没有nums[i+1]的所有子集加入res
解法四:位运算,源数组有n个元素,子集就有2^n个,将源数组的每个元素也用n为二进制来表示,所有子集的可能取值也用一个n位二进制来表示
比如
Input:[1,2,3]
分析:
用A、B、C代表源数组的0、1、2位元素
令A=001;B=010;C=100
那么子集为001表示只有C,101表示有A和C
所有子集的大小在0到2^3-1=7之间,也是0到1<<3-1之间
每个子集的可能取值&1<i(i为当前元素在源数组的位置)的结果等于1<i的话说明第i个元素在这个子集中,就将nums[i]加入temp中。
代码:
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> res = new ArrayList();
int allSets = 1<<nums.length;//最大结果值+1
for(int i=0;i<allSets;i++){//遍历所有可能的结果值
List<Integer> temp = new ArrayList();
//遍历nums集合中,看每个元素是否应该加入
for(int j=0;j<nums.length;j++){
if((i&(1<<j))==(1<<j)){
temp.add(nums[j]);
}
}
res.add(temp);
}
return res;
}