leetcode-78 子集的位运算解法

704 阅读3分钟

原题链接

子集

题目描述

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

  • 示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
  • 示例 2:
输入:nums = [0]
输出:[[],[0]]

解法

nums = [1, 2, 3]为例

首先贴上官方解法的一张表,其由0/1 序列子集0/1 序列对应的二进制数

0/1 序列子集0/1 序列对应的二进制数
000{}0
001{3}1
010{2}2
011{2, 3}3
100{1}4
101{1, 3}5
110{1, 2}6
111{1, 2, 3}7

可以看出来,我们用1代表取这一位数,用0代表不取这一位数,一共可以得到如上8种结果

  • 那么第一个问题来了,一个长度为n的数组,一共有几种结果呢?

答案是2 ^ n种结果,因为n长度的数组则有n个位置,全沾满了就是11...1一共n1,将其转为二进制,就是2 ^ n - 1,再加上0,则是2 ^ n

2 ^ n用二进制来表示,就是1 << n

这样,我们写出第一段代码

var subset = function (nums) {
    const n = nums.length;
    for (let i = 0; i < (1 << n); i++) {
        // do something...
    }
}

这样,我们可以循环所有的0/1 序列,从000111(以nums = [1, 2, 3]为例),新建一个临时数组tmp

var subset = function (nums) {
    const n = nums.length;
    const ret = [];
    for (let i = 0; i < (1 << n); i++) {
        const tmp = [];
        for (let j = 0; j < n; j++) {
            if ( // do something... ) {
                tmp.push( // valid number );
            }
        }
        ret.push(tmp);
    }
}

再循环整个nums,判断当前数字是否存在于当前0/1 序列里,如果有,则将该数字放入tmp中,循环完nums后,再将tmp放入返回的数组ret

  • 第二个问题来了,如何判断这个当前这个数字是否存在于当前这个0/1 序列中呢?

例如现在有0/1 序列101,其表示的子集{1, 3},当循环到13的时候,如何判断他就存在于0/1 序列101中呢?

其实这里我们判断0/1 序列哪几个下标是1就可以了,因为0/1 序列的下标和nums的下标是一一对应的

例如,对于数字1来说,我们判断0/1 序列101的第一位是不是1

我们先将101右移两位101 >> 2这样找到他的第一位

再将第一位1进行&运算,若为1,则这一位1,否则为0

所以if里的判断条件就可以写成(i >> j) & 1 === 1(其实这里的j等同于n - j - 1,只不过是将0/1 序列的顺序颠倒一下,也就是0/1 序列101对应的子集{3,1})

补充进去:

var subset = function (nums) {
    const n = nums.length;
    const ret = [];
    for (let i = 0; i < (1 << n); i++) {
        const tmp = [];
        for (let j = 0; j < n; j++) {
            if ((i >> j) & 1 === 1) {
                tmp.push(nums[j]);
            }
        }
        ret.push(tmp);
    }
    return ret;
}

二进制求子集

二进制求子集就是在二进制的1位上不断减1,而0位保持不变

这样每次减1得到的新数就是一个子集

例如:10100

直接减一后:10011

此时第四个0位发生了变化,如何复位呢?

与原数进行&运算即可

这样:原数中有0的位则在新数中也为0;原数中有1的位则在新数中按照新数的数为准,在该位上,新数是1则为1,新数是0则为0

&运算后得到结果:10000,这样成功把第三位的1去掉

结语

leetcode的官方题解我没太看懂,查了很多资料才明白代码里的一堆位运算是什么意思,写下这篇文章希望对你有所帮助

状态压缩总结的比较好的小技巧:leetcode.cn/problems/pa…

其他状态压缩的题目练习