一、矢量构造法
具体实现我个人解释不清楚,看代码吧
public class Subset {
public List<List<Integer>> Vector_construction(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
res.add(new ArrayList<>());//先把空集添加进去
int i, j, n = nums.length;
for (i = 0; i < n; ++i) {//将第i个数加入前置所有集合中,这边一定不能写i<nums.lengh
//for (j = res.size() - 1; j >= 0; --j) {
//j-1是res list中最后一个集合的位置,这边可以改成先用一个temp存储res的大小,然后再从0开始遍历
int size = res.size();
for (j = 0; j < size; j++) {
List<Integer> tmp = new ArrayList<>(res.get(j));
tmp.add(nums[i]);
res.add(tmp);
}
}
return res;//返回结果
}
public static void main(String[] args) {
int[] nums = new int[]{1, 2, 3, 4};
Subset subset = new Subset();
//test 1
System.out.println(subset.Vector_construction(nums));
}
}
二、位向量法
实际上就是暴力搜索DFS
利用boolean数组表示第i个选还是不选
多加几个参数会减小代码的可读性,但是可以提升代码的运行效率,减少使用全局变量
这里DFS回溯还有一种写法是利用for循环来递归,这里用了一个参数代替了for循环,具体不过多赘述
public class Subset {
public void Bit_vector(int[] nums, int n, int index, Boolean[] is_checked, List<List<Integer>> res) {
if (index == n) {
List<Integer> tmp = new ArrayList<>();
for (int i = 0; i < n; ++i)
if (is_checked[i]) tmp.add(nums[i]);
res.add(tmp);//存储结果
return;//递归结束条件
}
//选
is_checked[index] = true;
Bit_vector(nums, n, index + 1, is_checked, res);
//不选
is_checked[index] = false;//回溯
Bit_vector(nums, n, index + 1, is_checked, res);
}
public static void main(String[] args) {
int[] nums = new int[]{1, 2, 3, 4};
Subset subset = new Subset();
//test 2
List<List<Integer>> paths = new ArrayList<>();
Boolean[] is_checked = new Boolean[nums.length];
subset.Bit_vector(nums, nums.length, 0, is_checked, paths);
}
}
三、位向量法改
直接往里面往path里面添加数据,添加后回溯,再递归
public class Subset {
public void Bit_vector(int[] nums, int n, int index, List<Integer> path, List<List<Integer>> paths) {
if (index == n) {
paths.add(new ArrayList<>(path));//复制一遍,不能直接add(path),地址问题
return;
}
//选
path.add(nums[index]);
Bit_vector(nums, n, index + 1, path, paths);
//不选
//好像可以用removelast,懒得改了
path.remove(path.size() - 1);//注意: 每次添加的元素是再path的末尾,而不是index位置上
Bit_vector(nums, n, index + 1, path, paths);
}
public static void main(String[] args) {
int[] nums = new int[]{1, 2, 3, 4};
Subset subset = new Subset();
//test 3
List<List<Integer>> paths = new ArrayList<>();
List<Integer> path = new ArrayList<>();
subset.Bit_vector(nums, nums.length, 0, new ArrayList<>(), paths);
System.out.println(paths);
}
}
四、 二进制法
利用二进制枚举,每个数都只有选和不选两种情况,共 种情况
那我们只需要枚举 [0 , -1] 所有的数即可,利用其来构造子集
0表示选,1表示不选,假如数数组为4,3,2,1,看下面的规律,懂得都懂
0 -> 0000 -> 空集 1 -> 0001 -> {1} 2 -> 0010 -> {2} 3 -> 0011 -> {2,1} 4 -> 0100 -> {3} 5 -> 0101 -> {3,1} 6 -> 0110 -> {3,2} ..... 15 -> 1111 ->{4,3,2,1}
public class Subset {
public List<List<Integer>> Binary(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
//Bitmask 是用来枚举的数
//maximum 实际上就是 2^1-1 ,可以用位移运算来计算其值,从而避免Math.pow()后要进行强转
int i, n = nums.length, Bitmask = 0, maximum = (1 << n) - 1;
while (Bitmask <= maximum) {
List<Integer> tmp = new ArrayList<>();
for (i = 0; i < n; ++i) {
//注意 从Bitmask的低位开始与 1 进行 '与' 运算,判断条件是大于0
//如果没有看懂位运算,可以自己写一个函数来进行判断
//二进制从右向左数第i位是不是1
if ((Bitmask & (1 << i)) > 0) {
tmp.add(nums[i]);
}
}
res.add(tmp);
++Bitmask;
}
return res;
}
public static void main(String[] args) {
int[] nums = new int[]{1, 2, 3, 4};
Subset subset = new Subset();
//test 4
System.out.println(subset.Binary(nums));
}
}