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位,检测最低一位是否为1if ((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;}
