我们经常会碰到这样的问题:给定一个集合,枚举其所有可能的子集
。当然,方法有很多,这里先介绍一种最简单的枚举方法----二进制法.
基本思想
思想很简单,我们可以用二进制的一位对应某一元素的选取状态,1
表示选取,0
表示未选取。
举个例子,假如要枚举{1, 2, 3, 4, 5}
的所有子集,首先我们知道,其子集个数为
2
5
2^5
25个 ,也即从00000
到11111
共32
个,那么二进制01101
就表示{2, 3, 5}
:
二进制数位 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
二进制数值 | 0 | 1 | 1 | 0 | 1 |
选取的元素 | - | 2 | 3 | - | 5 |
基于上述,我们的关键是要知道一个二进制数有哪些位是1?
那么有一个基本技巧,假设一个二进制数为num
,我们只需要将num
和1 << j
进行与(&)
操作,就能判断num
的第j
位数字是否为1
。
比如:
num
=11
,j
=3
num
的二进制表示为1011
,1 << j
的二进制表示为1000
,
1011 & 1000 = 1000 ≠ 0
,那么可以得出num
二进制表示的第3
位(最低位记为第0
位)上的数字为1
;
num
=11
,j
=2
num
的二进制表示为1011
,1 << j
的二进制表示为0100
,
1011 & 0100 = 0000 = 0
,那么可以得出num
二进制表示的第2
位上的数字为0
.
核心代码
// 给定集合nums: [1, 2, 3]
int n = nums.length;
List<List<Integer>> res = new ArrayList<>();
// 大小为n的集合子集可用1 << n 种二进制数值表示, 取值区间为[0, 1 << n)
for (int i = 0; i < (1 << n); i++) {
List<Integer> list = new ArrayList<>();
for (int j = 0; j < n; j++) {
if ((i & (1 << j)) != 0) { // 表示下标为j的元素是否被选取
list.add(nums[j]);
}
}
res.add(list);
}
res:
[
[],
[1],
[1,2],
[1,2,3],
[1,3],
[2],
[2,3],
[3]
]