原题链接
题目描述
给你一个整数数组 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…