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>
7.8 KiB
7.8 KiB
子集 (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] <= 10nums中的所有元素 互不相同
解题思路
方法一:回溯法(推荐)
**核心思想:**对于每个元素,可以选择包含或不包含。使用回溯法生成所有可能的组合。
算法步骤:
- 初始化结果数组和当前子集
- 定义回溯函数
backtrack(start):- 将当前子集加入结果
- 从
start开始遍历,依次尝试包含每个元素 - 递归调用后撤销选择(回溯)
方法二:迭代法(位掩码)
**核心思想:**子集可以用二进制表示。对于 n 个元素,共有 2^n 个子集。
算法步骤:
- 计算子集总数
total = 1 << n - 对于每个数字
i从 0 到total-1:- 将
i的二进制表示转换为子集 - 第
j位为 1 表示包含nums[j]
- 将
方法三:级联法
**核心思想:**对于已有的每个子集,通过添加当前元素生成新的子集。
算法步骤:
- 初始化结果为
[[]] - 对于每个元素:
- 取出所有已有子集
- 将当前元素添加到每个子集
- 将新子集加入结果
代码实现
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))
}
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
}
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: 需要先排序,然后在回溯时跳过重复元素。
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: 在回溯时添加终止条件。
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. 深度理解:为什么子集问题适合用回溯法?
回溯法的本质:
- 在解空间树中进行深度优先搜索
- 每个节点代表一个决策(包含或不包含当前元素)
- 通过撤销选择(回溯)来探索所有可能
为什么适合子集问题:
- **决策清晰:**每个元素只有两种选择(包含或不包含)
- **无后效性:**当前选择不影响之前的选择
- **边界明确:**子集大小从 0 到 n
2. 实战扩展:组合与排列
**组合问题:**从 n 个元素中选 k 个,不考虑顺序 **排列问题:**从 n 个元素中选 k 个,考虑顺序
// 组合
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,返回该数组所有可能的子集(幂集)。
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