在算法与编程中,子集枚举是一个经典问题,即给定一个长度为 `n` 的序列 `a`,如何生成其所有可能的子序列(包括空集)。递归是解决这类组合问题的自然方法,因为它能直观地模拟“选择”与“不选择”的过程。本文将详细介绍如何用递归实现子集枚举,并分析其时间与空间复杂度。
给定序列 `a = [a₁, a₂, ..., aₙ]`,其子集是指从原序列中任意选取元素(包括空集和全集)构成的集合。例如:
- 序列 `[1, 2]` 的子集为:`[], [1], [2], [1, 2]`
子集枚举的目标是**系统性地生成所有可能的子集**,通常用于解决组合优化、搜索等问题。
核心思想
递归的本质是**分治**:将问题分解为更小的子问题,直到达到基本情况。对于子集枚举,递归的核心逻辑是:
- **每一步对当前元素做出选择**:选或不选。
- **递归处理剩余元素**,直到遍历完所有元素。
示例(以 `a = [1, 2]` 为例)
```
开始([])
├─ 选择1 → [1]
│ ├─ 选择2 → [1, 2]
│ └─ 不选2 → [1]
└─ 不选1 → []
├─ 选择2 → [2]
└─ 不选2 → []
```
最终结果:`[], [1], [1, 2], [2]`
代码
class Main {
List<Integer> temp = new ArrayList<>();
List<List<Integer>> ans = new ArrayList<>();
public List<List<Integer>> subsets(int n) {
dfs(1, n);
return ans;
}
public void dfs(int cur, int n) {
if (cur > n) {
ans.add(new ArrayList<>(temp)); // 保存当前子集
return;
}
// 选择当前元素
temp.add(cur);
dfs(cur + 1, n);
temp.remove(temp.size() - 1); // 回溯
// 不选择当前元素
dfs(cur + 1, n);
}
}
复杂度分析
时间复杂度:O(2ⁿ)
每个元素有“选”或“不选”两种可能,共 n个元素,总子集数为 2ⁿ。
空间复杂度:O(n)
递归栈深度最大为 n,且 path占用 O(n) 空间。
结语
递归枚举子集是理解回溯算法的基础,其核心在于**选择与撤销选择的对称性**。虽然时间复杂度较高,但在小规模数据或组合问题中仍非常实用。掌握后,可进一步学习剪枝优化(如分支限界)或迭代法(如位运算枚举)。
7321

被折叠的 条评论
为什么被折叠?



