【探索-中级算法】子集

在这里插入图片描述

这里题目没有要求子集内的元素必须升序,也没有要求所有子集的排列顺序,且没有重复的元素。

参考自:http://www.cnblogs.com/grandyang/p/4309345.html

解法一 迭代
对于题目中给的例子[1,2,3]来说,最开始是空集,那么我们现在要处理1,就在空集上加1,为[1],现在
有两个自己[]和[1],下面来处理2,我们在之前的子集基础上,每个都加个2,可以分别得到[2],[1, 2],
那么现在所有的子集合为[], [1], [2], [1, 2],同理处理3的情况可得[3], [1, 3], [2, 3], 
[1, 2, 3], 再加上之前的子集就是所有的子集合了
public List<List<Integer>> subsets(int[] nums) {
    List<List<Integer>> result = new LinkedList<>();
    result.add(new LinkedList<>());
    if (nums != null && nums.length > 0) {
        for (int i = 0; i < nums.length; i++) {
            List<List<Integer>> tmpResult = new ArrayList<>();
            System.out.println(result.size());
            for (List<Integer> subList:result) {
                List<Integer> tmp = new ArrayList<>(subList);
                tmp.add(nums[i]);
                tmpResult.add(tmp);
            }
            result.addAll(tmpResult);
        }
    }
    return result;
}

解法二 递归
由于原集合每一个数字只有两种状态,要么存在,要么不存在,那么在构造子集时就有选择和不选择两种情况,
所以可以构造一棵二叉树,左子树表示选择该层处理的节点,右子树表示不选择,最终的叶节点就是所有子集合,
树的结构如下:

                        []        
                   /          \        
                  /            \     
                 /              \
              [1]                []
           /       \           /    \
          /         \         /      \        
       [1 2]       [1]       [2]     []
      /     \     /   \     /   \    / \
  [1 2 3] [1 2] [1 3] [1] [2 3] [2] [3] []
List<List<Integer>> result = new LinkedList<>();

public List<List<Integer>> subsets(int[] nums) {
    if (nums != null && nums.length > 0) {
        dfs(nums, 0,new LinkedList<>());
    }
    return result;
}

public void dfs(int[] nums,int i,List<Integer> tmp) {
    if (i == nums.length) {
        result.add(tmp);
        return;
    }
    List<Integer> another = new ArrayList<>(tmp);
    another.add(nums[i]);
    dfs(nums, i + 1, tmp);
    dfs(nums, i + 1, another);
}

解法三
把数组中所有的数分配一个状态,true表示这个数在子集中出现,false表示在子集中不出现,那么对于一个
长度为n的数组,每个数字都有出现与不出现两种情况,所以共有2n中情况,那么我们把每种情况都转换出来
就是子集了

为了更加直观的讲解,对于 true 用 1 代替,false 则用 0 代替。

则对于 [1, 2, 3],总共有 8 个子集,对于每一个子集,都由子集序数的二进制进一步映射得到,比如第 0 个子集,此时子集序数为 0,其二进制为 000,而每一位 0 则代表对应位置上的 nums[idnex] 不在第 0 个子集中,因此就有:
在这里插入图片描述

public List<List<Integer>> subsets(int[] nums) {
    List<List<Integer>> result = new ArrayList<>();
    // 得到子集个数,即相当于 2^nums.length,因为是左移 nums.length 位
    int max = 1 << nums.length;
    for (int i = 0; i < max; i++) { // 根据子集序数进行遍历
        result.add(convertIntToSet(nums,i));
    }
    return result;
}

public List<Integer> convertIntToSet(int[] nums,int i) {
    List<Integer> list = new ArrayList<>();
    int index = 0;
    // 每次都右移一位,结合与运算,好判断二进制数的每一位是 1 还是 0
    // 循环次数与 i 对应的二进制的较高位第一次出现的 1 有关
    // (100) 总共右移 3 次,而 (010) 则只右移 2 次
    for (int j = i; j > 0; j >>= 1) {
    	// 对 nums[index] 进行映射
        if ((j & 1) == 1) list.add(nums[index]);
        ++index;
    }
    return list;
}

解法利用了组合的规律,很巧妙,且因为没有重复元素,所以在处理上比较简单直接。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值