Skip to content

Commit 87cdb8f

Browse files
committed
add post
1 parent b8955f3 commit 87cdb8f

1 file changed

Lines changed: 135 additions & 0 deletions

File tree

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
---
2+
layout: post
3+
title: "子集生成"
4+
date: 2019-08-30
5+
categories:: 编程
6+
tags:
7+
- leetcode
8+
- 算法
9+
- 暴力求解
10+
---
11+
很多问题都可以“**暴力解决**”。不需要动太多脑筋,把所有的可能性都列出来,然后一一实验。这样的方法显得很“笨”,却往往是行之有效的。 并且,很多问题拆分后的子问题,也需要用暴力求解的思想,比如 `BFS` 搜索最短路径,就需要列出所有可能,然后加入队列。
12+
13+
本篇讨论暴力求解的其中一个问题,子集生成问题。其他暴力求解的问题,如简单枚举,枚举排列,回溯法,路径寻找(隐式图的遍历)等问题,本篇暂不讨论。
14+
15+
## 子集生成问题
16+
给定一个集合,枚举出所有可能的子集。[leetcode 第 78 题](https://leetcode.com/problems/subsets/)
17+
18+
所有的自己生成问题都可以用三种方法来解决。**增量构造法****位向量法**,和**二进制法**
19+
20+
### 增量构造法
21+
思路是每次选出一个元素放入集合中。
22+
23+
比如对于题目中给的例子 `[1,2,3]` 来说,最开始是空集,那么我们现在要处理 `1`,就在空集上加 `1`,为 `[1]`,现在我们有两个自己 `[]``[1]`,下面我们来处理 `2`,我们在之前的子集基础上,每个都加个 `2`,可以分别得到 `[2]``[1, 2]`,那么现在所有的子集合为 `[]`, `[1]`, `[2]`, `[1, 2]`,同理处理 `3` 的情况可得 `[3]`, `[1, 3]`, `[2, 3]`, `[1, 2, 3]`, 再加上之前的子集就是所有的子集合了,代码如下:
24+
```c++
25+
vector<vector<int>> subsets(vector<int> &S) {
26+
vector<vector<int>> res(1);
27+
sort(S.begin(), S.end());
28+
for (int i = 0; i < S.size(); i++) {
29+
int size = res.size();
30+
for (int j = 0; j < size; j++) {
31+
res.push_back(res[j]);
32+
res.back().push_back(S[i]);
33+
}
34+
}
35+
return res;
36+
}
37+
```
38+
整个添加的顺序为:
39+
```
40+
[]
41+
[1]
42+
[2]
43+
[1 2]
44+
[3]
45+
[1 3]
46+
[2 3]
47+
[1 2 3]
48+
```
49+
50+
### 位向量法
51+
由于原集合每一个数字只有两种状态,要么存在,要么不存在,那么在构造子集时就有选择和不选择两种情况,所以可以构造一棵二叉树,左子树表示选择该层处理的节点,右子树表示不选择,最终的叶节点就是所有子集合,树的结构如下:
52+
```
53+
[]
54+
/ \
55+
/ \
56+
/ \
57+
[1] []
58+
/ \ / \
59+
/ \ / \
60+
[1 2] [1] [2] []
61+
/ \ / \ / \ / \
62+
[1 2 3] [1 2] [1 3] [1] [2 3] [2] [3] []
63+
```
64+
代码如下:
65+
```c++
66+
vector<vector<int>> subsets(vector<int> &S) {
67+
vector<vector<int>> res;
68+
vector<int> path;
69+
sort(S.begin(), S.end());
70+
genSubsets(S, 0, path, res);
71+
return res;
72+
}
73+
void genSubsets(vector<int> &S, int pos, vector<int> &path, vector<vector<int>> &res) {
74+
res.push_back(path);
75+
for (int i = pos; i < S.size(); i++) {
76+
path.push_back(S[i]);
77+
genSubsets(S, i + 1, path, res);
78+
path.pop_back();
79+
}
80+
}
81+
```
82+
83+
整个添加的顺序为:
84+
```
85+
[]
86+
[1]
87+
[1 2]
88+
[1 2 3]
89+
[1 3]
90+
[2]
91+
[2 3]
92+
[3]
93+
```
94+
95+
### 二进制法
96+
由于集合中每个元素只有两种可能,选 与 不选。正好对应二进制的 `1``0`。于是,很自然的想到用二进制数字来表示集合的选择情况。
97+
98+
下面是二进制数字和对应的集合。
99+
100+
| | 1 | 2 | 3 | Subset |
101+
|-------|-------|---------|---------|-----------|
102+
| 0 | 0 | 0 | 0 |[] |
103+
| 1 | 0 | 0 | 1 |[3] |
104+
| 2 | 0 | 1 | 0 |[2] |
105+
| 3 | 0 | 1 | 1 |[2,3] |
106+
| 4 | 1 | 0 | 0 |[1] |
107+
| 5 | 1 | 0 | 1 |[1,3] |
108+
| 6 | 1 | 1 | 0 |[1,2] |
109+
| 7 | 1 | 1 | 1 |[1,2,3] |
110+
111+
对应的代码如下:
112+
```c++
113+
vector<vector<int>> subsets(vector<int> &S) {
114+
vector<vector<int>> res;
115+
sort(S.being(), S.end());
116+
int max = 1 << S.size();
117+
for (int k = 0; k < max; k++) {
118+
vector<int> out = genSubset(S, k);
119+
res.push_back(out);
120+
}
121+
return res;
122+
}
123+
124+
vector<int> genSubset(vector<int> &S, int k) {
125+
vector<int> sub;
126+
int idx = 0;
127+
for (int i = k; i > 0; i = i >> 1) {
128+
if ((i & 1) == 1) {
129+
sub.push_back(S[idx]);
130+
}
131+
idx++;
132+
}
133+
return sub;
134+
}
135+
```

0 commit comments

Comments
 (0)