Files
interview/16-LeetCode Hot 100/子集.md
yasinshaw 4247e0700d refactor: convert all LeetCode solutions to Go-only
Changes:
- Removed all Java code implementations
- Kept only Go language solutions
- Renamed "## Go 解法" to "## 解法"
- Removed "### Go 代码要点" sections
- Cleaned up duplicate headers and empty sections
- Streamlined documentation for better readability

Updated files (9):
- 三数之和.md
- 两数相加.md
- 无重复字符的最长子串.md
- 最长回文子串.md
- 括号生成.md
- 子集.md
- 单词搜索.md
- 电话号码的字母组合.md
- 柱状图中最大的矩形.md

All 22 LeetCode Hot 100 Medium problems now use Go exclusively.
Code is cleaner, more focused, and easier to follow.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-03-05 12:32:55 +08:00

369 lines
7.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 子集 (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` 中的所有元素 **互不相同**
## 解题思路
### 方法一:回溯法(推荐)
**核心思想:**对于每个元素,可以选择包含或不包含。使用回溯法生成所有可能的组合。
**算法步骤:**
1. 初始化结果数组和当前子集
2. 定义回溯函数 `backtrack(start)`
- 将当前子集加入结果
-`start` 开始遍历,依次尝试包含每个元素
- 递归调用后撤销选择(回溯)
### 方法二:迭代法(位掩码)
**核心思想:**子集可以用二进制表示。对于 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)
- 需要存储所有子集
## 进阶问题
### 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