Skip to content

Commit 5261ecd

Browse files
author
杨世超
committed
Update 0078. 子集.md
1 parent b6c3567 commit 5261ecd

File tree

1 file changed

+90
-11
lines changed

1 file changed

+90
-11
lines changed

Solutions/0078. 子集.md

Lines changed: 90 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,104 @@
55

66
## 题目大意
77

8-
给定一个整数数组 nums,数组中的元素互不相同,返回该数组所有可能的不重复子集。
8+
**描述**:给定一个整数数组 `nums`,数组中的元素互不相同。
9+
10+
**要求**:返回该数组所有可能的不重复子集。
11+
12+
**示例**
13+
14+
```Python
15+
输入 nums = [1,2,3]
16+
输出 [[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
17+
```
918

1019
## 解题思路
1120

12-
回溯算法,遍历数组 nums。为了使得子集不重复,每次遍历从当前位置的下一个位置进行下一层遍历。
21+
### 思路 1:回溯算法
22+
23+
数组的每个元素都有两个选择:选与不选。
24+
25+
我们可以通过向当前子集数组中添加可选元素来表示选择该元素。也可以在当前递归结束之后,将之前添加的元素从当前子集数组中移除(也就是回溯)来表示不选择该元素。
26+
27+
回溯算法解决这道题的步骤如下:
28+
29+
- 从第 `0` 个位置开始,调用 `backtrack` 方法进行深度优先搜索。
30+
- 将当前子集数组 `sub_set` 添加到答案数组 `sub_sets` 中。
31+
- 然后从当前位置开始,到数组结束为止,枚举出所有可选的元素。对于每一个可选元素:
32+
- 将其添加到当前子集数组 `sub_set` 中。
33+
- 在选择该元素的情况下,继续递归考虑下一个元素。
34+
- 进行回溯,撤销选择该元素。即从当前子集数组 `sub_set` 中移除之前添加的元素。
35+
36+
### 思路 2:二进制枚举
37+
38+
对于一个元素个数为 `n` 的集合 `nums` 来说,每一个位置上的元素都有选取和未选取两种状态。我们可以用数字 `1` 来表示选取该元素,用数字 `0` 来表示不选取该元素。
39+
40+
那么我们就可以用一个长度为 `n` 的二进制数来表示集合 `nums` 或者表示 `nums` 的子集。其中二进制的每一位数都对应了集合中某一个元素的选取状态。对于集合中第 `i` 个元素(`i``0` 开始编号)来说,二进制对应位置上的 `1` 代表该元素被选取,`0` 代表该元素未被选取。
41+
42+
举个例子来说明一下,比如长度为 `5` 的集合 `nums = {5, 4, 3, 2, 1}`,我们可以用一个长度为 `5` 的二进制数来表示该集合。
43+
44+
比如二进制数 `11111` 就表示选取集合的第 `0` 位、第 `1` 位、第 `2` 位、第 `3` 位、第 `4` 位元素,也就是集合 `{5, 4, 3, 2, 1}` ,即集合 `nums` 本身。如下表所示:
45+
46+
| 集合 nums 对应位置(下标) | 4 | 3 | 2 | 1 | 0 |
47+
| :------------------------- | :--: | :--: | :--: | :--: | :--: |
48+
| 对应选取状态 | 选取 | 选取 | 选取 | 选取 | 选取 |
49+
| 二进制数对应位数 | 1 | 1 | 1 | 1 | 1 |
50+
51+
再比如二进制数 `10101` 就表示选取集合的第 `0` 位、第 `2` 位、第 `5` 位元素,也就是集合 `{5, 3, 1}`。如下表所示:
52+
53+
| 集合 nums 对应位置(下标) | 4 | 3 | 2 | 1 | 0 |
54+
| :------------------------- | :--: | :----: | :--: | :----: | :--: |
55+
| 对应选取状态 | 选取 | 未选取 | 选取 | 未选取 | 选取 |
56+
| 二进制数对应位数 | 1 | 0 | 1 | 0 | 1 |
57+
58+
再比如二进制数 `01001` 就表示选取集合的第 `0` 位、第 `3` 位元素,也就是集合 `{5, 2}`。如下标所示:
59+
60+
| 集合 nums 对应位置(下标) | 4 | 3 | 2 | 1 | 0 |
61+
| :------------------------- | :----: | :--: | :----: | :----: | :--: |
62+
| 对应选取状态 | 未选取 | 选取 | 未选取 | 未选取 | 选取 |
63+
| 二进制数对应位数 | 0 | 1 | 0 | 0 | 1 |
64+
65+
通过上面的例子我们可以得到启发:对于长度为 `5` 的集合 `nums` 来说,我们只需要从 `00000` ~ `11111` 枚举一次(对应十进制为 $0 \sim 2^4 - 1$)即可得到长度为 `5` 的集合 `S` 的所有子集。
66+
67+
我们将上面的例子拓展到长度为 `n` 的集合 `nums`。可以总结为:
68+
69+
- 对于长度为 `5` 的集合 `nums` 来说,只需要枚举 $0 \sim 2^n - 1$(共 $2^n$ 种情况),即可得到所有的子集。
1370

1471
## 代码
1572

73+
### 思路 1 :回溯算法代码
74+
75+
```Python
76+
class Solution:
77+
def backtrack(self, sub_sets, nums, sub_set, start):
78+
sub_sets.append(sub_set[:])
79+
80+
for i in range(start, len(nums)):
81+
sub_set.append(nums[i])
82+
self.backtrack(sub_sets, nums, sub_set, i + 1)
83+
sub_set.pop()
84+
return sub_sets
85+
86+
def subsets(self, nums: List[int]) -> List[List[int]]:
87+
if not nums:
88+
return []
89+
sub_sets = []
90+
return self.backtrack(sub_sets, nums, [], 0)
91+
```
92+
93+
### 思路 2:二进制枚举代码
94+
1695
```Python
1796
class Solution:
1897
def subsets(self, nums: List[int]) -> List[List[int]]:
19-
def backtrack(size, subset, index):
20-
res.append(subset)
21-
for i in range(index, size):
22-
backtrack(size, subset + [nums[i]], i + 1)
23-
24-
size = len(nums)
25-
res = list()
26-
backtrack(size, [], 0)
27-
return res
98+
n = len(nums) # n 为集合 nums 的元素个数
99+
sub_sets = [] # sub_sets 用于保存所有子集
100+
for i in range(1 << n): # 枚举 0 ~ 2^n - 1
101+
sub_set = [] # sub_set 用于保存当前子集
102+
for j in range(n): # 枚举第 i 位元素
103+
if i >> j & 1: # 如果第 i 为元素对应二进制位删改为 1,则表示选取该元素
104+
sub_set.append(nums[j]) # 将选取的元素加入到子集 sub_set 中
105+
sub_sets.append(sub_set) # 将子集 sub_set 加入到所有子集数组 sub_sets 中
106+
return sub_sets # 返回所有子集
28107
```
29108

0 commit comments

Comments
 (0)