78.子集
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1: 输入:nums = [1,2,3] 输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
示例 2: 输入:nums = [0] 输出:[[],[0]]
提示: 1 <= nums.length <= 10 -10 <= nums[i] <= 10 nums 中的所有元素 互不相同
解法1:回溯法
- 思路:就是选和不选的问题,若选择了这个数字,则在perm集合里加上,若不选择,则直接递归到下一层。怎么解决选与不选:让循环i初始化时等于current,然后递归下去时用i+1来代替current+1,这时一种方法;而另一种方法则是不用for循环,而是直接选,然后递归下去,若干不选则是直接递归,这种方法就是下面代码内容。
- 流程图
static List<List<Integer>> res;
public static List<List<Integer>> subsets(int[] nums) {
res = new ArrayList<>();
Deque<Integer> perm = new ArrayDeque<>();
dfs(nums, 0, perm);
return res;
}
private static void dfs(int[] nums, int current, Deque<Integer> perm) {
if (current == nums.length) {
res.add(new ArrayList<>(perm));
return;
}
//其实就是选和不选的问题
perm.addLast(nums[current]);
dfs(nums, current + 1, perm);
perm.removeLast();
//不选
dfs(nums, current + 1, perm);
}
解法2:二进制法
- 思路:两层循环,第一层循环从0开始,循环的次数是子集的个数,第二层循环是子集的长度,从子集的长度减1开始到0结束。如果二进制位上为1,则把这个数字加进集合perm中,然后perm加进结果集合中。如1,2,3,有2^3=8个子集,则第一层要从0循环到7,0代表的二进制为000,1为001,2为010等等依次类推,第二层循环从子集长度-1为2开始0结束,0因为每一位都为0,所以加空集进perm中,1中的第二和第一位都为0,第一位为1,所以把3加进perm中,后面的结果依次类推。最后得到全部子集。
public static List<List<Integer>> subsets2(int[] nums) {
List<List<Integer>> ans = new ArrayList<>();
int len = nums.length;
Arrays.sort(nums);
for (int i = 0; i < (int) Math.pow(2, len); i++) {
List<Integer> perm = new ArrayList<>();
for (int j = len - 1; j >= 0; j--) {
//i >> j & 1是把i右移j位,检测最低一位是否为1
if ((i >> j & 1) == 1) {
perm.add(nums[j]);
}
}
ans.add(perm);
}
return ans;
}
90.子集II
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
示例 1: 输入:nums = [1,2,2] 输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]
示例 2: 输入:nums = [0] 输出:[[],[0]]
提示: 1 <= nums.length <= 10 -10 <= nums[i] <= 10
解法1:回溯法
- 思路:这种解法同样有两种不同的代码写法,但是去重思路都是相同的。运用一个标志位,当这个元素的上一个标志位为false且num[i-1]==num[i]时,此时就不用把当前元素加进集合中。注意用循环时每次都要把当前的perm加入到结果中,而不用循环时要把不选放在选的后面。
- 流程图
static List<List<Integer>> res;
public static List<List<Integer>> subsetsWithDup3(int[] nums) {
res = new ArrayList<>();
Deque<Integer> perm = new ArrayDeque<>();
boolean[] vis = new boolean[nums.length];
Arrays.sort(nums);
dfs2(nums, 0, perm, vis);
return res;
}
private static void dfs2(int[] nums, int current, Deque<Integer> perm, boolean[] vis) {
//先把当前路径加入到结果中
res.add(new ArrayList<>(perm));
int len = nums.length;
if (current == len) {
return;
}
for (int i = current; i < len; i++) {
if (i > 0 && nums[i - 1] == nums[i] && !vis[i - 1]) {
continue;
}
perm.addLast(nums[i]);
vis[i] = true;
dfs2(nums, i + 1, perm, vis);
vis[i] = false;
perm.removeLast();
}
}
static List<List<Integer>> res;
public static List<List<Integer>> subsetsWithDup2(int[] nums) {
res = new ArrayList<>();
Deque<Integer> perm = new ArrayDeque<>();
Arrays.sort(nums);
dfs(false,nums, 0, perm);
return res;
}
//画图帮助理解
private static void dfs(boolean choose,int[] nums, int current, Deque<Integer> perm) {
if (current == nums.length) {
res.add(new ArrayList<>(perm));
return;
}
//不选
dfs(false, nums, current + 1, perm);
//去重,上一层是不选的,且nums[current - 1] == nums[current]证明这个数是重复的
if (!choose && current > 0 && nums[current - 1] == nums[current]) {
return;
}
//选
perm.addLast(nums[current]);
dfs(true, nums, current + 1, perm);
perm.removeLast();
}
解法2:二进制法
思路:去重思路与解法1是相同的,若num[i-1]==num[i]时且没有选i-1这个数时,此时就要去重。
public static List<List<Integer>> subsetsWithDup(int[] nums) {
List<List<Integer>> ans = new ArrayList<>();
int len = nums.length;
Arrays.sort(nums);
for (int i = 0; i < (int) Math.pow(2, len); i++) {
List<Integer> perm = new ArrayList<>();
boolean flag = true;
for (int j = len - 1; j >= 0; j--) {
if ((i >> j & 1) == 1) {
//找出规律可知,当nums[j - 1] == nums[j]且此时j-1位为0时,会输出重复的
if (j > 0 && (i >> (j - 1) & 1) == 0 && nums[j - 1] == nums[j]) {
flag = false;
break;
}
perm.add(nums[j]);
//System.out.println("perm=="+perm);
}
}
/*//用集合去重
if (!ans.contains(perm)) {
ans.add(perm);
}*/
if (flag) {
ans.add(perm);
}
}
return ans;
}