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

449 lines
9.9 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))
}
```
### Java 实现(回溯法)
```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 实现(迭代法-位掩码)
```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 实现(迭代法-位掩码)
```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 实现(级联法)
```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