Files
interview/16-LeetCode Hot 100/子集.md
yasinshaw e75e4778b1 feat: add 19 LeetCode Hot 100 medium problems with detailed solutions
批量生成 19 道 LeetCode Hot 100 Medium 难度题目,每道题包含:
- 题目描述和示例
- 多种解题思路(回溯、DP、双指针等)
- Go 和 Java 双语解答
- 完整的测试用例
- 复杂度分析
- 进阶问题
- P7 加分项(深度理解、实战扩展、变形题目)

新增题目:
1. 盛最多水的容器 (Container With Most Water) - LeetCode 11
2. 电话号码的字母组合 (Letter Combinations) - LeetCode 17
3. 删除链表的倒数第N个结点 - LeetCode 19
4. 括号生成 - LeetCode 22
5. 最长回文子串 - LeetCode 5
6. 子集 - LeetCode 78
7. 单词搜索 - LeetCode 79
8. 柱状图中最大的矩形 - LeetCode 84
9. 最大正方形 - LeetCode 221
10. 完全平方数 - LeetCode 279
11. 最长连续序列 - LeetCode 128
12. 除自身以外数组的乘积 - LeetCode 238
13. 最小栈 - LeetCode 155
14. 二叉树的中序遍历 - LeetCode 94
15. 二叉树的最大深度 - LeetCode 104
16. 翻转二叉树 - LeetCode 226
17. 对称二叉树 - LeetCode 101
18. 路径总和 - LeetCode 112
19. 从前序与中序遍历序列构造二叉树 - LeetCode 105

所有代码均包含:
- 清晰的注释说明
- 完整的可运行测试用例
- 时间和空间复杂度分析
- 优化技巧和变形题目

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-05 12:26:07 +08:00

9.9 KiB
Raw Blame History

子集 (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 实现(回溯法)

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))
}

Java 实现(回溯法)

import java.util.ArrayList;
import java.util.List;

public class Subsets {

    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        List<Integer> current = new ArrayList<>();
        backtrack(result, current, nums, 0);
        return result;
    }

    private void backtrack(List<List<Integer>> result, List<Integer> current,
                          int[] nums, int start) {
        // 将当前子集加入结果
        result.add(new ArrayList<>(current));

        // 从 start 开始尝试包含每个元素
        for (int i = start; i < nums.length; i++) {
            // 选择当前元素
            current.add(nums[i]);
            // 递归处理下一个元素
            backtrack(result, current, nums, i + 1);
            // 撤销选择(回溯)
            current.remove(current.size() - 1);
        }
    }

    // 测试用例
    public static void main(String[] args) {
        Subsets solution = new Subsets();

        // 测试用例1
        int[] nums1 = {1, 2, 3};
        System.out.println("输入: [1, 2, 3]");
        System.out.println("输出: " + solution.subsets(nums1));

        // 测试用例2
        int[] nums2 = {0};
        System.out.println("\n输入: [0]");
        System.out.println("输出: " + solution.subsets(nums2));

        // 测试用例3
        int[] nums3 = {1, 2};
        System.out.println("\n输入: [1, 2]");
        System.out.println("输出: " + solution.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
}

Java 实现(迭代法-位掩码)

public List<List<Integer>> subsetsBitMask(int[] nums) {
    int n = nums.length;
    int total = 1 << n; // 2^n 个子集
    List<List<Integer>> result = new ArrayList<>();

    for (int mask = 0; mask < total; mask++) {
        List<Integer> subset = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            // 检查第 i 位是否为 1
            if ((mask & (1 << i)) != 0) {
                subset.add(nums[i]);
            }
        }
        result.add(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: 需要先排序,然后在回溯时跳过重复元素。

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. 深度理解:为什么子集问题适合用回溯法?

回溯法的本质:

  • 在解空间树中进行深度优先搜索
  • 每个节点代表一个决策(包含或不包含当前元素)
  • 通过撤销选择(回溯)来探索所有可能

为什么适合子集问题:

  1. **决策清晰:**每个元素只有两种选择(包含或不包含)
  2. **无后效性:**当前选择不影响之前的选择
  3. **边界明确:**子集大小从 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