原题链接
题目描述
给你一个整数数组 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一共n个1,将其转为二进制,就是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 序列,从000到111(以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},当循环到1和3的时候,如何判断他就存在于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…