# 子集 (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< 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