力扣78.子集
力扣刷题 20

力扣78.子集

原题,题目描述:

5852eeaf-30c3-4a94-ba19-18e8c8354972.png

题目会给我们一个数组(集合),我们需要找出这个集合的所有子集,包括空集。

思路一

题目的描述很短,但是这道题我真实的想了好久。然后我看了一下这道题的标签,是回溯,于是我写下了如下代码:

class Solution {
    ArrayList<List<Integer>> subsetsRes = new ArrayList<>();

    public List<List<Integer>> subsets(int[] nums) {
        subsetsRes.add(new ArrayList<>());
        int len = nums.length;
        ArrayList<Integer> list = new ArrayList<>();
        if (len==1){
            list.add(nums[0]);

            subsetsRes.add(list);
            return subsetsRes;//subsetsRes.stream().toList();
        }
        for (int i : nums) {
            list.add(i);
        }
        subsetsRes.add(list);
        int[] t = new int[len - 1];
        for (int i = 0; i < len; i++) {
            System.arraycopy(nums, 0, t, 0, i);
            System.arraycopy(nums, i+1, t, i, len-i-1);
            subsets2(t);
        }
        return subsetsRes;//subsetsRes.stream().toList();
    }

    public void subsets2(int[] nums) {
        int len = nums.length;
        ArrayList<Integer> list = new ArrayList<>();
        if (len==1){
            list.add(nums[0]);
            for (List<Integer> r : subsetsRes) {
                if (r.equals(list)) {
                    return;
                }
            }
            subsetsRes.add(list);
            return;
        }
        for (int i : nums) {
            list.add(i);
        }
        for (List<Integer> r : subsetsRes) {
            if (r.equals(list)) {
                return;
            }
        }
        subsetsRes.add(list);
        int[] t = new int[len - 1];
        for (int i = 0; i < len; i++) {
            System.arraycopy(nums, 0, t, 0, i);
            System.arraycopy(nums, i+1, t, i, len-i-1);
            subsets2(t);
        }
    }

}

顺序去掉数组中的元素传入下一递归,然后去掉重复的元素。毫无疑问,去重操作是非常耗时的,重复操作递归也非常耗时。所以这份答案的结果虽然通过,但是非常不理想。

我思考了很久,确实有点没想法了,就去看了官解,新学习了如下两种解法

解法二

我们可以逐步的添加元素至集合中,每次添加都和前面的元素融合,如下图:

QQ_1771242283158.png

了解解法后我们就可以写出如下代码:

class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        ArrayList<List<Integer>> res = new ArrayList<>();
        res.add(new ArrayList<>());
        for (int num : nums) {
            int size = res.size();
            for (int i = 0; i < size; i++) {
                ArrayList<Integer> newList = new ArrayList<>(res.get(i));
                newList.add(num);
                res.add(newList);
            }
        }
        return res;
    }
}

代码非常的简短,速度也是杠杠的,打败百分之百。

解法三

第三种解法是我觉得最有意思的:我们可以借助二进制数来构建子集,以集合{1,2,3}为力,我们选取一个 最低3位位1,其他位,除符号位为0的数字,做为for循环的终点数字。从0开始递增,我们,以上面的集合为例,这个数字为7,我们做for,会得到的数字序列为 0~7,二进制数为 000,001 010 011 ...... 110 111,我们使用数字的位置作为下标去数组里选取数字,如果对应的二进制位为0则不选,为1就将数字加入到数组。下面看代码:

class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        ArrayList<List<Integer>> res = new ArrayList<>();
        int len = nums.length;
        int target = 0;
        for (int i = 0; i < len; i++) {
            target = target << 1;
            target = target | 1;
        }

        for (int i = 0; i <= target; i++) {
            int temp = i;
            ArrayList<Integer> list = new ArrayList<>();
            for (int j = 0; j < len && temp != 0; j++) {
                if((temp&1)==1){
                    list.add(nums[len-1-j]);
                }
                temp=temp>>>1;
            }
            res.add(list);
        }
        return res;
    }
}

这个解法的速度也不错。

上面的解法时空复杂度都是 O(​n\times 2n),不过对于这种问题,这个是正常且必须的时空复杂度,因为展开后信息是爆炸性的。

力扣78.子集
https://www.than.pw/archives/019c663a-117e-7248-87fb-541a823aad2f
作者
than
发布于
更新于
许可