按照改进方案,为以下6个二叉树题目增强了解题思路的详细程度: 1. 二叉树的中序遍历 - 增加"思路推导"部分,解释递归到迭代的转换 - 详细说明迭代法的每个步骤 - 增加执行过程演示和多种解法 2. 二叉树的最大深度 - 增加"思路推导",对比DFS和BFS - 详细解释递归的基准情况 - 增加多种解法和变体问题 3. 从前序与中序遍历序列构造二叉树 - 详细解释前序和中序的特点 - 增加"思路推导",说明如何分治 - 详细说明切片边界计算 4. 对称二叉树 - 解释镜像对称的定义 - 详细说明递归比较的逻辑 - 增加迭代解法和变体问题 5. 翻转二叉树 - 解释翻转的定义和过程 - 详细说明多值赋值的执行顺序 - 增加多种解法和有趣的故事 6. 路径总和 - 详细解释路径和叶子节点的定义 - 说明为什么使用递减而非累加 - 增加多种解法和变体问题 每个文件都包含: - 完整的示例和边界条件分析 - 详细的算法流程和图解 - 关键细节说明 - 常见错误分析 - 复杂度分析(详细版) - 执行过程演示 - 多种解法 - 变体问题 - 总结 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
778 lines
17 KiB
Markdown
778 lines
17 KiB
Markdown
# 子集 (Subsets)
|
||
|
||
## 题目描述
|
||
|
||
给你一个整数数组 `nums`,数组中的元素 **互不相同**。返回该数组所有可能的子集(幂集)。
|
||
|
||
解集 **不能** 包含重复的子集。你可以按 **任意顺序** 返回解集。
|
||
|
||
### 示例
|
||
|
||
**示例 1:**
|
||
```
|
||
输入:nums = [1,2,3]
|
||
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
|
||
```
|
||
|
||
**示例 2:**
|
||
```
|
||
输入:nums = [0]
|
||
输出:[[],[0]]
|
||
```
|
||
|
||
### 约束条件
|
||
|
||
- `1 <= nums.length <= 10`
|
||
- `-10 <= nums[i] <= 10`
|
||
- `nums` 中的所有元素 **互不相同**
|
||
|
||
## 思路推导
|
||
|
||
### 暴力解法分析
|
||
|
||
**第一步:直观思路 - 枚举所有可能的子集**
|
||
|
||
```python
|
||
def subsets_brute(nums):
|
||
n = len(nums)
|
||
result = []
|
||
|
||
# 遍历所有可能的子集掩码
|
||
for mask in range(1 << n): # 0 到 2^n - 1
|
||
subset = []
|
||
for i in range(n):
|
||
if mask & (1 << i): # 检查第 i 位是否为 1
|
||
subset.append(nums[i])
|
||
result.append(subset)
|
||
|
||
return result
|
||
```
|
||
|
||
**时间复杂度分析:**
|
||
- 有 2^n 个子集
|
||
- 每个子集需要 O(n) 时间构建
|
||
- **总时间复杂度:O(n × 2^n)**
|
||
|
||
**问题:**
|
||
- 虽然时间复杂度已经是最优的,但位运算不易理解
|
||
- 代码可读性较差
|
||
- 难以扩展到带约束条件的子集问题
|
||
|
||
### 优化思考 - 如何更直观地生成子集?
|
||
|
||
**核心观察:**
|
||
1. **子集的本质**:对每个元素,都有"选"或"不选"两种选择
|
||
2. **决策树视角**:n 个元素构成一个 n 层的决策树
|
||
3. **回溯法**:自然地表达这种"选择-撤销"的过程
|
||
|
||
**为什么用回溯?**
|
||
- 更直观地表达选择过程
|
||
- 易于剪枝(如有约束条件)
|
||
- 可以生成子集的同时进行处理
|
||
|
||
### 为什么这样思考?
|
||
|
||
**1. 二叉选择视角**
|
||
```
|
||
对于 [1,2,3]:
|
||
|
||
[]
|
||
/ \
|
||
不选1 选1
|
||
[] [1]
|
||
/ \ / \
|
||
不选2 选2 不选2 选2
|
||
[] [2] [1] [1,2]
|
||
/ \ / \ / \ / \
|
||
3 [] 3 [2] ... (继续展开)
|
||
|
||
叶子节点就是所有子集
|
||
```
|
||
|
||
**2. 回溯法的优势**
|
||
```
|
||
- 每个节点代表一个决策点
|
||
- 自然地表达"选"或"不选"
|
||
- 可以在任意时刻处理当前子集
|
||
- 易于添加约束条件(如子集和限制)
|
||
```
|
||
|
||
## 解题思路
|
||
|
||
### 方法一:回溯法(推荐)
|
||
|
||
**核心思想:**对于每个元素,可以选择包含或不包含。使用回溯法生成所有可能的组合。
|
||
|
||
**算法步骤:**
|
||
1. 初始化结果数组和当前子集
|
||
2. 定义回溯函数 `backtrack(start)`:
|
||
- 将当前子集加入结果
|
||
- 从 `start` 开始遍历,依次尝试包含每个元素
|
||
- 递归调用后撤销选择(回溯)
|
||
|
||
### 详细算法流程
|
||
|
||
**步骤1:理解回溯框架**
|
||
|
||
```python
|
||
def backtrack(start):
|
||
# 将当前子集加入结果
|
||
result.append(current[:])
|
||
|
||
# 从 start 开始尝试包含每个元素
|
||
for i in range(start, len(nums)):
|
||
# 选择当前元素
|
||
current.append(nums[i])
|
||
# 递归处理下一个元素
|
||
backtrack(i + 1)
|
||
# 撤销选择(回溯)
|
||
current.pop()
|
||
```
|
||
|
||
**Q: 为什么从 start 而不是从 0 开始?**
|
||
|
||
A: 避免重复生成相同的子集。举例:
|
||
```
|
||
nums = [1, 2]
|
||
|
||
如果每次都从 0 开始:
|
||
- 选 1: current=[1]
|
||
- 选 2: current=[1,2] ✓
|
||
- 选 1: current=[1,1] ✗ 重复!
|
||
|
||
如果从 start 开始:
|
||
- i=0: 选 1: current=[1]
|
||
- i=1: 选 2: current=[1,2] ✓
|
||
- i=1: 选 2: current=[2] ✓
|
||
```
|
||
|
||
**步骤2:理解为何要加入空集**
|
||
|
||
```python
|
||
result.append(current[:]) # 在循环前就加入
|
||
```
|
||
|
||
**Q: 为什么在循环前就加入结果?**
|
||
|
||
A: 因为每个中间状态都是一个有效的子集。举例:
|
||
```
|
||
nums = [1, 2, 3]
|
||
|
||
执行过程:
|
||
1. current=[] → 加入 []
|
||
2. 选择 1: current=[1] → 加入 [1]
|
||
3. 选择 2: current=[1,2] → 加入 [1,2]
|
||
4. 选择 3: current=[1,2,3] → 加入 [1,2,3]
|
||
5. 回溯:current=[1,2]
|
||
6. 回溯:current=[1]
|
||
7. 选择 3: current=[1,3] → 加入 [1,3]
|
||
...
|
||
```
|
||
|
||
**步骤3:理解回溯的撤销**
|
||
|
||
```python
|
||
current.append(nums[i]) # 做选择
|
||
backtrack(i + 1)
|
||
current.pop() # 撤销选择
|
||
```
|
||
|
||
**Q: 为什么必须撤销?**
|
||
|
||
A: 因为 `current` 是共享的列表,不撤销会影响后续递归。
|
||
|
||
举例说明撤销的重要性:
|
||
```
|
||
不撤销的情况:
|
||
- 选择 1: current=[1]
|
||
- 选择 2: current=[1,2],加入结果
|
||
- 回溯(但没有撤销)
|
||
- 选择 3: current=[1,2,3] ✗ 应该是 [1,3]
|
||
|
||
正确撤销:
|
||
- 选择 1: current=[1]
|
||
- 选择 2: current=[1,2],加入结果
|
||
- 回溯并撤销:current=[1]
|
||
- 选择 3: current=[1,3] ✓
|
||
```
|
||
|
||
### 关键细节说明
|
||
|
||
**细节1:为什么用 current[:] 而不是 current?**
|
||
|
||
```python
|
||
# 错误写法
|
||
result.append(current) # 添加引用
|
||
|
||
# 正确写法
|
||
result.append(current[:]) # 添加副本
|
||
```
|
||
|
||
**为什么?**
|
||
- `current` 是可变列表,后续修改会影响已加入结果的数据
|
||
- `current[:]` 创建副本,保证结果不被修改
|
||
|
||
**细节2:为什么循环从 start 开始?**
|
||
|
||
```python
|
||
for i in range(start, len(nums)): # 从 start 开始
|
||
current.append(nums[i])
|
||
backtrack(i + 1) # 下次从 i+1 开始
|
||
current.pop()
|
||
```
|
||
|
||
**为什么?**
|
||
- 避免重复:确保子集中的元素按原数组顺序出现
|
||
- 例如:[1,2] 会出现,但 [2,1] 不会出现
|
||
|
||
**细节3:如何理解生成所有子集?**
|
||
|
||
```
|
||
nums = [1, 2, 3]
|
||
|
||
回溯树:
|
||
[]
|
||
/ | \
|
||
不选2 选2 选3 (错误理解)
|
||
|
|
||
选3
|
||
|
||
正确理解(按顺序):
|
||
[]
|
||
/ | \
|
||
[1] [2] [3]
|
||
/ \ |
|
||
[1,2] [1,3] [2,3]
|
||
|
|
||
[1,2,3]
|
||
|
||
每个节点都是一个有效的子集!
|
||
```
|
||
|
||
### 边界条件分析
|
||
|
||
**边界1:空数组**
|
||
```
|
||
输入:nums = []
|
||
输出:[[]]
|
||
原因:空集是任何集合的子集
|
||
```
|
||
|
||
**边界2:单个元素**
|
||
```
|
||
输入:nums = [1]
|
||
输出:[[], [1]]
|
||
过程:
|
||
- 初始:current=[],加入 []
|
||
- 选择 1:current=[1],加入 [1]
|
||
```
|
||
|
||
**边界3:所有元素相同**
|
||
```
|
||
输入:nums = [1, 1, 1]
|
||
输出:[[], [1], [1,1], [1,1,1]]
|
||
注意:题目说元素互不相同,所以这种情况不会出现
|
||
```
|
||
|
||
### 复杂度分析(详细版)
|
||
|
||
**时间复杂度:**
|
||
```
|
||
- 子集数量:2^n
|
||
- 每个子集的构建:O(n)(最坏情况)
|
||
- **总时间复杂度:O(n × 2^n)**
|
||
|
||
为什么每个子集是 O(n)?
|
||
- 虽然子集长度不同,但平均长度是 n/2
|
||
- 总元素数 = 0×C(n,0) + 1×C(n,1) + ... + n×C(n,n) = n×2^(n-1)
|
||
- 平均每个子集的元素数 = n×2^(n-1) / 2^n = n/2
|
||
```
|
||
|
||
**空间复杂度:**
|
||
```
|
||
- 递归栈深度:O(n)
|
||
- 存储结果:O(n × 2^n)(所有子集的总元素数)
|
||
- **空间复杂度:O(n)**(不计结果存储)
|
||
|
||
### 方法二:迭代法(位掩码)
|
||
|
||
**核心思想:**子集可以用二进制表示。对于 n 个元素,共有 2^n 个子集。
|
||
|
||
**算法步骤:**
|
||
1. 计算子集总数 `total = 1 << n`
|
||
2. 对于每个数字 `i` 从 0 到 `total-1`:
|
||
- 将 `i` 的二进制表示转换为子集
|
||
- 第 `j` 位为 1 表示包含 `nums[j]`
|
||
|
||
### 方法三:级联法
|
||
|
||
**核心思想:**对于已有的每个子集,通过添加当前元素生成新的子集。
|
||
|
||
**算法步骤:**
|
||
1. 初始化结果为 `[[]]`
|
||
2. 对于每个元素:
|
||
- 取出所有已有子集
|
||
- 将当前元素添加到每个子集
|
||
- 将新子集加入结果
|
||
|
||
## 代码实现
|
||
|
||
### Go 实现(回溯法)
|
||
|
||
```go
|
||
package main
|
||
|
||
import "fmt"
|
||
|
||
func subsets(nums []int) [][]int {
|
||
result := [][]int{}
|
||
current := []int{}
|
||
|
||
var backtrack func(start int)
|
||
backtrack = func(start int) {
|
||
// 将当前子集加入结果(需要复制)
|
||
temp := make([]int, len(current))
|
||
copy(temp, current)
|
||
result = append(result, temp)
|
||
|
||
// 从 start 开始尝试包含每个元素
|
||
for i := start; i < len(nums); i++ {
|
||
// 选择当前元素
|
||
current = append(current, nums[i])
|
||
// 递归处理下一个元素
|
||
backtrack(i + 1)
|
||
// 撤销选择(回溯)
|
||
current = current[:len(current)-1]
|
||
}
|
||
}
|
||
|
||
backtrack(0)
|
||
return result
|
||
}
|
||
|
||
// 测试用例
|
||
func main() {
|
||
// 测试用例1
|
||
nums1 := []int{1, 2, 3}
|
||
fmt.Printf("输入: %v\n", nums1)
|
||
fmt.Printf("输出: %v\n", subsets(nums1))
|
||
|
||
// 测试用例2
|
||
nums2 := []int{0}
|
||
fmt.Printf("\n输入: %v\n", nums2)
|
||
fmt.Printf("输出: %v\n", subsets(nums2))
|
||
|
||
// 测试用例3
|
||
nums3 := []int{1, 2}
|
||
fmt.Printf("\n输入: %v\n", nums3)
|
||
fmt.Printf("输出: %v\n", subsets(nums3))
|
||
}
|
||
```
|
||
|
||
```go
|
||
func subsetsBitMask(nums []int) [][]int {
|
||
n := len(nums)
|
||
total := 1 << n // 2^n 个子集
|
||
result := make([][]int, 0, total)
|
||
|
||
for mask := 0; mask < total; mask++ {
|
||
subset := []int{}
|
||
for i := 0; i < n; i++ {
|
||
// 检查第 i 位是否为 1
|
||
if mask&(1<<i) != 0 {
|
||
subset = append(subset, nums[i])
|
||
}
|
||
}
|
||
result = append(result, subset)
|
||
}
|
||
|
||
return result
|
||
}
|
||
```
|
||
|
||
```go
|
||
func subsetsCascade(nums []int) [][]int {
|
||
result := [][]int{{}} // 初始化为空集
|
||
|
||
for _, num := range nums {
|
||
// 对于每个已有子集,添加当前元素生成新子集
|
||
newSubsets := make([][]int, 0, len(result))
|
||
for _, subset := range result {
|
||
newSubset := make([]int, len(subset)+1)
|
||
copy(newSubset, subset)
|
||
newSubset[len(subset)] = num
|
||
newSubsets = append(newSubsets, newSubset)
|
||
}
|
||
result = append(result, newSubsets...)
|
||
}
|
||
|
||
return result
|
||
}
|
||
```
|
||
|
||
## 复杂度分析
|
||
|
||
### 回溯法
|
||
|
||
- **时间复杂度:** O(n × 2^n)
|
||
- 共有 2^n 个子集
|
||
- 每个子集的复制需要 O(n) 时间
|
||
|
||
- **空间复杂度:** O(n)
|
||
- 递归栈深度最大为 n
|
||
- 不包括存储结果的空间
|
||
|
||
### 迭代法(位掩码)
|
||
|
||
- **时间复杂度:** O(n × 2^n)
|
||
- 需要生成 2^n 个子集
|
||
- 每个子集需要 O(n) 时间构建
|
||
|
||
- **空间复杂度:** O(1)
|
||
- 只使用了常数级别的额外空间(不包括结果)
|
||
|
||
### 级联法
|
||
|
||
- **时间复杂度:** O(n × 2^n)
|
||
- 每次迭代都会将子集数量翻倍
|
||
- 总共需要处理 n 次
|
||
|
||
- **空间复杂度:** O(n × 2^n)
|
||
- 需要存储所有子集
|
||
|
||
## 执行过程演示
|
||
|
||
以 `nums = [1, 2, 3]` 为例:
|
||
|
||
```
|
||
初始状态:result=[], current=[], start=0
|
||
|
||
第1层递归 (start=0):
|
||
result=[[]] # 加入空集
|
||
循环:i 从 0 到 2
|
||
|
||
i=0, nums[0]=1:
|
||
current=[1]
|
||
递归 backtrack(1)
|
||
├─ result=[[], [1]] # 加入 [1]
|
||
├─ i=1, nums[1]=2:
|
||
│ current=[1,2]
|
||
│ 递归 backtrack(2)
|
||
│ ├─ result=[[], [1], [1,2]] # 加入 [1,2]
|
||
│ ├─ i=2, nums[2]=3:
|
||
│ │ current=[1,2,3]
|
||
│ │ 递归 backtrack(3)
|
||
│ │ ├─ result=[[], [1], [1,2], [1,2,3]] # 加入 [1,2,3]
|
||
│ │ └─ 返回
|
||
│ │ current=[1,2] # 撤销 3
|
||
│ └─ 返回
|
||
│ current=[1] # 撤销 2
|
||
├─ i=2, nums[2]=3:
|
||
│ current=[1,3]
|
||
│ 递归 backtrack(3)
|
||
│ ├─ result=[[], [1], [1,2], [1,2,3], [1,3]] # 加入 [1,3]
|
||
│ └─ 返回
|
||
│ current=[1] # 撤销 3
|
||
└─ 返回
|
||
current=[] # 撤销 1
|
||
|
||
i=1, nums[1]=2:
|
||
current=[2]
|
||
递归 backtrack(2)
|
||
├─ result=[[], [1], [1,2], [1,2,3], [1,3], [2]] # 加入 [2]
|
||
├─ i=2, nums[2]=3:
|
||
│ current=[2,3]
|
||
│ 递归 backtrack(3)
|
||
│ ├─ result=[[], [1], [1,2], [1,2,3], [1,3], [2], [2,3]] # 加入 [2,3]
|
||
│ └─ 返回
|
||
│ current=[2] # 撤销 3
|
||
└─ 返回
|
||
current=[] # 撤销 2
|
||
|
||
i=2, nums[2]=3:
|
||
current=[3]
|
||
递归 backtrack(3)
|
||
├─ result=[[], [1], [1,2], [1,2,3], [1,3], [2], [2,3], [3]] # 加入 [3]
|
||
└─ 返回
|
||
current=[] # 撤销 3
|
||
|
||
最终结果:[[], [1], [1,2], [1,2,3], [1,3], [2], [2,3], [3]]
|
||
```
|
||
|
||
## 常见错误
|
||
|
||
### 错误1:忘记复制 current
|
||
|
||
❌ **错误写法:**
|
||
```go
|
||
func subsets(nums []int) [][]int {
|
||
result := [][]int{}
|
||
current := []int{}
|
||
|
||
var backtrack func(start int)
|
||
backtrack = func(start int) {
|
||
result = append(result, current) // 错误!添加引用
|
||
for i := start; i < len(nums); i++ {
|
||
current = append(current, nums[i])
|
||
backtrack(i + 1)
|
||
current = current[:len(current)-1]
|
||
}
|
||
}
|
||
|
||
backtrack(0)
|
||
return result
|
||
}
|
||
```
|
||
|
||
✅ **正确写法:**
|
||
```go
|
||
func subsets(nums []int) [][]int {
|
||
result := [][]int{}
|
||
current := []int{}
|
||
|
||
var backtrack func(start int)
|
||
backtrack = func(start int) {
|
||
temp := make([]int, len(current))
|
||
copy(temp, current) // 复制
|
||
result = append(result, temp)
|
||
|
||
for i := start; i < len(nums); i++ {
|
||
current = append(current, nums[i])
|
||
backtrack(i + 1)
|
||
current = current[:len(current)-1]
|
||
}
|
||
}
|
||
|
||
backtrack(0)
|
||
return result
|
||
}
|
||
```
|
||
|
||
**原因:**Go 中切片是引用类型,直接添加会导致所有结果都是同一个切片的引用。
|
||
|
||
### 错误2:循环从 0 开始而不是 start
|
||
|
||
❌ **错误写法:**
|
||
```go
|
||
for i := 0; i < len(nums); i++ { // 错误:从 0 开始
|
||
current = append(current, nums[i])
|
||
backtrack(i + 1)
|
||
current = current[:len(current)-1]
|
||
}
|
||
```
|
||
|
||
✅ **正确写法:**
|
||
```go
|
||
for i := start; i < len(nums); i++ { // 正确:从 start 开始
|
||
current = append(current, nums[i])
|
||
backtrack(i + 1)
|
||
current = current[:len(current)-1]
|
||
}
|
||
```
|
||
|
||
**原因:**会导致重复生成相同的子集。
|
||
|
||
### 错误3:忘记撤销选择
|
||
|
||
❌ **错误写法:**
|
||
```go
|
||
for i := start; i < len(nums); i++ {
|
||
current = append(current, nums[i])
|
||
backtrack(i + 1)
|
||
// 忘记撤销
|
||
}
|
||
```
|
||
|
||
✅ **正确写法:**
|
||
```go
|
||
for i := start; i < len(nums); i++ {
|
||
current = append(current, nums[i])
|
||
backtrack(i + 1)
|
||
current = current[:len(current)-1] // 必须撤销
|
||
}
|
||
```
|
||
|
||
**原因:**不撤销会导致后续递归使用错误的 current。
|
||
|
||
## 进阶问题
|
||
|
||
### Q1: 如果数组中有重复元素,应该如何处理?
|
||
|
||
**A:** 需要先排序,然后在回溯时跳过重复元素。
|
||
|
||
```go
|
||
func subsetsWithDup(nums []int) [][]int {
|
||
sort.Ints(nums)
|
||
result := [][]int{}
|
||
current := []int{}
|
||
|
||
var backtrack func(start int)
|
||
backtrack = func(start int) {
|
||
temp := make([]int, len(current))
|
||
copy(temp, current)
|
||
result = append(result, temp)
|
||
|
||
for i := start; i < len(nums); i++ {
|
||
// 跳过重复元素
|
||
if i > start && nums[i] == nums[i-1] {
|
||
continue
|
||
}
|
||
current = append(current, nums[i])
|
||
backtrack(i + 1)
|
||
current = current[:len(current)-1]
|
||
}
|
||
}
|
||
|
||
backtrack(0)
|
||
return result
|
||
}
|
||
```
|
||
|
||
### Q2: 如果要求子集的大小恰好为 k,应该如何修改?
|
||
|
||
**A:** 在回溯时添加终止条件。
|
||
|
||
```go
|
||
func subsetsK(nums []int, k int) [][]int {
|
||
result := [][]int{}
|
||
current := []int{}
|
||
|
||
var backtrack func(start int)
|
||
backtrack = func(start int) {
|
||
if len(current) == k {
|
||
temp := make([]int, len(current))
|
||
copy(temp, current)
|
||
result = append(result, temp)
|
||
return
|
||
}
|
||
|
||
for i := start; i < len(nums); i++ {
|
||
current = append(current, nums[i])
|
||
backtrack(i + 1)
|
||
current = current[:len(current)-1]
|
||
}
|
||
}
|
||
|
||
backtrack(0)
|
||
return result
|
||
}
|
||
```
|
||
|
||
## P7 加分项
|
||
|
||
### 1. 深度理解:为什么子集问题适合用回溯法?
|
||
|
||
**回溯法的本质:**
|
||
- 在解空间树中进行深度优先搜索
|
||
- 每个节点代表一个决策(包含或不包含当前元素)
|
||
- 通过撤销选择(回溯)来探索所有可能
|
||
|
||
**为什么适合子集问题:**
|
||
1. **决策清晰:**每个元素只有两种选择(包含或不包含)
|
||
2. **无后效性:**当前选择不影响之前的选择
|
||
3. **边界明确:**子集大小从 0 到 n
|
||
|
||
### 2. 实战扩展:组合与排列
|
||
|
||
**组合问题:**从 n 个元素中选 k 个,不考虑顺序
|
||
**排列问题:**从 n 个元素中选 k 个,考虑顺序
|
||
|
||
```go
|
||
// 组合
|
||
func combine(n int, k int) [][]int {
|
||
result := [][]int{}
|
||
current := []int{}
|
||
|
||
var backtrack func(start int)
|
||
backtrack = func(start int) {
|
||
if len(current) == k {
|
||
temp := make([]int, len(current))
|
||
copy(temp, current)
|
||
result = append(result, temp)
|
||
return
|
||
}
|
||
|
||
for i := start; i <= n; i++ {
|
||
current = append(current, i)
|
||
backtrack(i + 1)
|
||
current = current[:len(current)-1]
|
||
}
|
||
}
|
||
|
||
backtrack(1)
|
||
return result
|
||
}
|
||
|
||
// 排列
|
||
func permute(nums []int) [][]int {
|
||
result := [][]int{}
|
||
current := []int{}
|
||
used := make([]bool, len(nums))
|
||
|
||
var backtrack func()
|
||
backtrack = func() {
|
||
if len(current) == len(nums) {
|
||
temp := make([]int, len(current))
|
||
copy(temp, current)
|
||
result = append(result, temp)
|
||
return
|
||
}
|
||
|
||
for i := 0; i < len(nums); i++ {
|
||
if used[i] {
|
||
continue
|
||
}
|
||
current = append(current, nums[i])
|
||
used[i] = true
|
||
backtrack()
|
||
current = current[:len(current)-1]
|
||
used[i] = false
|
||
}
|
||
}
|
||
|
||
backtrack()
|
||
return result
|
||
}
|
||
```
|
||
|
||
### 3. 变形题目
|
||
|
||
#### 变形1:子集 II(有重复元素)
|
||
|
||
**LeetCode 90:** 给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
|
||
|
||
```go
|
||
func subsetsWithDup(nums []int) [][]int {
|
||
sort.Ints(nums)
|
||
result := [][]int{}
|
||
current := []int{}
|
||
|
||
var backtrack func(start int)
|
||
backtrack = func(start int) {
|
||
temp := make([]int, len(current))
|
||
copy(temp, current)
|
||
result = append(result, temp)
|
||
|
||
for i := start; i < len(nums); i++ {
|
||
if i > start && nums[i] == nums[i-1] {
|
||
continue
|
||
}
|
||
current = append(current, nums[i])
|
||
backtrack(i + 1)
|
||
current = current[:len(current)-1]
|
||
}
|
||
}
|
||
|
||
backtrack(0)
|
||
return result
|
||
}
|
||
```
|
||
|
||
### 4. 相关题目推荐
|
||
|
||
- LeetCode 78: 子集(本题)
|
||
- LeetCode 90: 子集 II
|
||
- LeetCode 77: 组合
|
||
- LeetCode 46: 全排列
|
||
- LeetCode 47: 全排列 II
|