分析
回溯法
- 思路
- 每个元素都有两种状态:选入当前子集或不选入当前子集
- 对每个元素进行选择,并递归处理剩余元素
- 当递归到数组末尾时,将当前子集
path
加入结果集 res
- 步骤
- 用
res
存储所有子集结果
- 用
path
存储当前子集路径
- 用递归函数
dfs(nums, u)
表示从索引 u
开始构造子集
- 如果索引到达数组末尾,将当前路径加入结果集
- 否则:
- 跳过当前元素,继续递归
- 包含当前元素,继续递归,并在递归结束后回溯状态
位运算枚举
-
位运算思路
- 每个子集可以看作长度为
n
的二进制数,每一位 1
表示对应的数组元素被选中
- 枚举从
0
到 2^n - 1
的所有整数,用二进制表示子集选择情况
- 对于每个整数:
- 遍历其二进制的每一位,若某一位为
1
,将对应位置的数组元素加入当前子集
-
步骤
- 初始化结果集
res
- 遍历从
0
到 2^n - 1
的整数,用位运算检查每一位是否为 1
- 若某位为
1
,将对应的数组元素加入当前子集 path
- 枚举结束后返回结果集
res
时间复杂度
每个元素有两种状态(选或不选),总状态数为 2^n
。遍历每个状态生成子集,总时间复杂度 O(n^2)
空间复杂度
递归调用栈深度为 O(n)
,路径数组 path
最多占用 O(n)
,总空间复杂度为 O(n)
C++代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
class Solution
{
public:
std::vector<std::vector<int>> res; // 存储所有子集
std::vector<int> path; // 当前子集路径
vector<vector<int>> subsets(vector<int>& nums)
{
dfs(nums, 0); // 从索引 0 开始回溯
return res;
}
void dfs(std::vector<int>& nums, int u)
{
// 递归终止条件:索引越界时,将当前路径加入结果集
if (u == nums.size())
{
res.push_back(path);
return;
}
// 不选择当前元素,继续递归
dfs(nums, u + 1);
// 选择当前元素,加入路径并继续递归
path.push_back(nums[u]);
dfs(nums, u + 1);
// 回溯状态,移除当前元素
path.pop_back();
}
};
class Solution
{
public:
vector<vector<int>> subsets(vector<int>& nums)
{
std::vector<std::vector<int>> res; // 存储所有子集
int n = nums.size();
// 枚举所有可能的子集,用二进制表示
for (int i = 0; i < (1 << n); ++i)
{
std::vector<int> path; // 当前子集
for (int j = 0; j < n; ++j)
{
if ((i >> j) & 1) // 若第 j 位为 1,加入对应元素
path.push_back(nums[j]);
}
res.push_back(path); // 将当前子集加入结果集
}
return res;
}
};
|