题目描述(中等难度)

78. Subsets - 图1

给一个数组,输出这个数组的所有子数组。

解法一 迭代一

77 题解法三一个思想,想找出数组长度 1 的所有解,然后再在长度为 1 的所有解上加 1 个数字变成长度为 2 的所有解,同样的直到 n。

假如 nums = [ 1, 2, 3 ],参照下图。

78. Subsets - 图2

  1. public List<List<Integer>> subsets(int[] nums) {
  2. List<List<Integer>> res = new ArrayList<List<Integer>>();
  3. List<List<Integer>> ans = new ArrayList<List<Integer>>();
  4. ans.add(new ArrayList<Integer>());
  5. res.add(new ArrayList<Integer>());
  6. int n = nums.length;
  7. // 第一层循环,子数组长度从 1 到 n
  8. for (int i = 1; i <= n; i++) {
  9. // 第二层循环,遍历上次的所有结果
  10. List<List<Integer>> tmp = new ArrayList<List<Integer>>();
  11. for (List<Integer> list : res) {
  12. // 第三次循环,对每个结果进行扩展
  13. for (int m = 0; m < n; m++) {
  14. //只添加比末尾数字大的数字,防止重复
  15. if (list.size() > 0 && list.get(list.size() - 1) >= nums[m])
  16. continue;
  17. List<Integer> newList = new ArrayList<Integer>(list);
  18. newList.add(nums[m]);
  19. tmp.add(newList);
  20. ans.add(newList);
  21. }
  22. }
  23. res = tmp;
  24. }
  25. return ans;
  26. }

解法二 迭代法2

参照这里。解法一的迭代法,是直接从结果上进行分类,将子数组的长度分为长度是 1 的,2 的 …. n 的。我们还可以从条件上入手,先只考虑给定数组的 1 个元素的所有子数组,然后再考虑数组的 2 个元素的所有子数组 … 最后再考虑数组的 n 个元素的所有子数组。求 k 个元素的所有子数组,只需要在 k - 1 个元素的所有子数组里边加上 nums [ k ] 即可。

例如 nums [1 , 2, 3] 的遍历过程。

78. Subsets - 图3

  1. public List<List<Integer>> subsets(int[] nums) {
  2. List<List<Integer>> ans = new ArrayList<>();
  3. ans.add(new ArrayList<>());//初始化空数组
  4. for(int i = 0;i<nums.length;i++){
  5. List<List<Integer>> ans_tmp = new ArrayList<>();
  6. //遍历之前的所有结果
  7. for(List<Integer> list : ans){
  8. List<Integer> tmp = new ArrayList<>(list);
  9. tmp.add(nums[i]); //加入新增数字
  10. ans_tmp.add(tmp);
  11. }
  12. ans.addAll(ans_tmp);
  13. }
  14. return ans;
  15. }

解法三 回溯法

参考这里。同样是很经典的回溯法例子,添加一个数,递归,删除之前的数,下次循环。

  1. public List<List<Integer>> subsets(int[] nums) {
  2. List<List<Integer>> ans = new ArrayList<>();
  3. getAns(nums, 0, new ArrayList<>(), ans);
  4. return ans;
  5. }
  6. private void getAns(int[] nums, int start, ArrayList<Integer> temp, List<List<Integer>> ans) {
  7. ans.add(new ArrayList<>(temp));
  8. for (int i = start; i < nums.length; i++) {
  9. temp.add(nums[i]);
  10. getAns(nums, i + 1, temp, ans);
  11. temp.remove(temp.size() - 1);
  12. }
  13. }

解法四 位操作

前方高能!!!!这个方法真的是太太太牛了。参考这里

数组的每个元素,可以有两个状态,子数组中和不在子数组中,所有状态的组合就是所有子数组了。

例如,nums = [ 1, 2 , 3 ]。1 代表在,0 代表不在。

  1. 1 2 3
  2. 0 0 0 -> [ ]
  3. 0 0 1 -> [ 3]
  4. 0 1 0 -> [ 2 ]
  5. 0 1 1 -> [ 2 3]
  6. 1 0 0 -> [1 ]
  7. 1 0 1 -> [1 3]
  8. 1 1 0 -> [1 2 ]
  9. 1 1 1 -> [1 2 3]

所以我们只需要遍历 0 0 0 到 1 1 1,也就是 0 到 7,然后判断每个比特位是否是 1,是 1 的话将对应数字加入即可。如果数组长度是 n,那么每个比特位是 2 个状态,所有总共就是 2 的 n 次方个子数组。遍历 00 … 0 到 11 … 1 即可。

  1. public List<List<Integer>> subsets(int[] nums) {
  2. List<List<Integer>> ans = new ArrayList<>();
  3. int bit_nums = nums.length;
  4. int ans_nums = 1 << bit_nums; //执行 2 的 n 次方
  5. for (int i = 0; i < ans_nums; i++) {
  6. List<Integer> tmp = new ArrayList<>();
  7. int count = 0; //记录当前对应数组的哪一位
  8. int i_copy = i; //用来移位
  9. while (i_copy != 0) {
  10. if ((i_copy & 1) == 1) { //判断当前位是否是 1
  11. tmp.add(nums[count]);
  12. }
  13. count++;
  14. i_copy = i_copy >> 1;//右移一位
  15. }
  16. ans.add(tmp);
  17. }
  18. return ans;
  19. }

同样是很经典的一道题,回溯,迭代,最后的位操作真的是太强了,每次遇到关于位操作的解法就很惊叹。

windliang wechat

添加好友一起进步~

如果觉得有帮助的话,可以点击 这里 给一个 star 哦 ^^

如果想系统的学习数据结构和算法,强烈推荐一个我之前学过的课程,可以点击 这里 查看详情