Files
interview/16-LeetCode Hot 100/最长连续序列.md
yasinshaw 5c1c974e88 docs: 改进LeetCode二叉树题目解题思路
按照改进方案,为以下6个二叉树题目增强了解题思路的详细程度:

1. 二叉树的中序遍历
   - 增加"思路推导"部分,解释递归到迭代的转换
   - 详细说明迭代法的每个步骤
   - 增加执行过程演示和多种解法

2. 二叉树的最大深度
   - 增加"思路推导",对比DFS和BFS
   - 详细解释递归的基准情况
   - 增加多种解法和变体问题

3. 从前序与中序遍历序列构造二叉树
   - 详细解释前序和中序的特点
   - 增加"思路推导",说明如何分治
   - 详细说明切片边界计算

4. 对称二叉树
   - 解释镜像对称的定义
   - 详细说明递归比较的逻辑
   - 增加迭代解法和变体问题

5. 翻转二叉树
   - 解释翻转的定义和过程
   - 详细说明多值赋值的执行顺序
   - 增加多种解法和有趣的故事

6. 路径总和
   - 详细解释路径和叶子节点的定义
   - 说明为什么使用递减而非累加
   - 增加多种解法和变体问题

每个文件都包含:
- 完整的示例和边界条件分析
- 详细的算法流程和图解
- 关键细节说明
- 常见错误分析
- 复杂度分析(详细版)
- 执行过程演示
- 多种解法
- 变体问题
- 总结

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-08 21:33:57 +08:00

706 lines
14 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.
# 最长连续序列 (Longest Consecutive Sequence)
LeetCode 128. Medium
## 题目描述
给定一个未排序的整数数组 `nums`,找出数字连续序列的最长长度。
**要求**:请设计时间复杂度为 O(n) 的算法。
**示例 1**
```
输入nums = [100, 4, 200, 1, 3, 2]
输出4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
```
**示例 2**
```
输入nums = [0, 3, 7, 2, 5, 8, 4, 6, 0, 1]
输出9
解释:最长的连续序列是 [0, 1, 2, 3, 4, 5, 6, 7, 8]。
```
## 思路推导
### 暴力解法分析
**最直观的思路**:排序后遍历,找到最长的连续序列。
```python
def longestConsecutive(nums):
if not nums:
return 0
nums.sort()
max_len = 1
current_len = 1
for i in range(1, len(nums)):
if nums[i] == nums[i-1] + 1:
current_len += 1
elif nums[i] != nums[i-1]: # 跳过重复
current_len = 1
max_len = max(max_len, current_len)
return max_len
```
**时间复杂度**O(n log n)
- 排序O(n log n)
- 遍历O(n)
- 总计O(n log n) + O(n) = O(n log n)
**空间复杂度**O(1) 或 O(n),取决于排序算法
**问题分析**
1. 不满足题目要求:题目要求 O(n)
2. 排序是最快的,但仍不够快
3. 需要寻找不排序的解法
### 优化思考 - 第一步:哈希表查找
**观察**:连续序列的特点是相邻元素相差 1
**问题**:如何快速判断一个数是否存在?
**解决方案**使用哈希表Set
```python
num_set = set(nums)
for num in nums:
# 检查 num+1 是否在集合中
if num + 1 in num_set:
# 继续检查 num+2, num+3, ...
```
**为什么这样思考?**
- 哈希表查找O(1)
- 可以快速判断一个数是否存在
- 不需要排序
### 优化思考 - 第二步:寻找序列起点
**关键优化**:如何避免重复计算同一个序列?
**观察**:只有当一个数是序列的起点时,才需要计算
```python
# num 是序列起点
if num - 1 not in num_set:
# 从 num 开始向后查找
current_num = num
current_len = 1
while current_num + 1 in num_set:
current_num += 1
current_len += 1
```
**为什么这样思考?**
- 如果 `num-1` 存在,说明 `num` 不是起点
- 只有起点才需要计算,避免重复
- 每个序列只被计算一次
**时间复杂度**O(n)
- 外层循环O(n)
- 内层 while总计 O(n)(每个元素只访问一次)
- 总计O(n) + O(n) = O(n)
### 优化思考 - 第三步:空间换时间
**权衡**
- 时间复杂度O(n)
- 空间复杂度O(n)
- 用空间换取时间
**为什么可以接受?**
- 题目要求 O(n) 时间
- O(n) 空间是可接受的
- 哈希表是实现 O(n) 的必要条件
## 解题思路
### 核心思想
**哈希表 + 序列起点判断**:用哈希表存储所有数字,只从序列起点开始计算长度。
**为什么这样思考?**
1. **哈希表的优势**
- O(1) 时间查找元素是否存在
- 无需排序,保持原始数据
2. **序列起点判断**
- 如果 `num-1` 不在集合中,`num` 是起点
- 只有起点才需要计算
- 避免重复计算同一个序列
3. **时间复杂度保证**
- 每个元素最多被访问 2 次
- 一次在哈希表中
- 一次在 while 循环中
### 详细算法流程
**步骤1构建哈希表**
```python
num_set = set(nums)
```
**作用**
- 快速判断元素是否存在
- O(1) 时间复杂度
**步骤2遍历所有数字**
```python
longest = 0
for num in num_set:
# 判断是否为序列起点
if num - 1 not in num_set:
# 从起点开始计算序列长度
current_num = num
current_len = 1
# 向后查找连续数字
while current_num + 1 in num_set:
current_num += 1
current_len += 1
# 更新最大长度
longest = max(longest, current_len)
```
**关键点详解**
1. **为什么判断 `num - 1 not in num_set`**
- 如果 `num-1` 存在,说明 `num` 不是起点
- 只有起点才需要计算
- 避免重复计算
**示例**
```
nums = [1, 2, 3, 4]
num=1: 1-1=0 不在集合中 → 起点,计算 [1,2,3,4]
num=2: 2-1=1 在集合中 → 不是起点,跳过
num=3: 3-1=2 在集合中 → 不是起点,跳过
num=4: 4-1=3 在集合中 → 不是起点,跳过
```
2. **为什么用 `while` 而不是 `for`**
- 不知道序列有多长
- 需要动态判断下一个数字是否存在
- `while` 更灵活
3. **为什么可以保证 O(n)**
- 外层 for 循环O(n)
- 内层 while 循环:总计 O(n)
- 每个元素只在 while 中被访问一次
- 因为只有起点才会进入 while
- 总计O(n) + O(n) = O(n)
### 关键细节说明
**细节1为什么用 `set` 而不是 `list`**
```python
# 推荐:使用 set
num_set = set(nums)
if num - 1 in num_set: # O(1)
# 不推荐:使用 list
if num - 1 in nums: # O(n)
```
**原因**
- `set` 的查找是 O(1)
- `list` 的查找是 O(n)
- 总复杂度会变成 O(n²)
**细节2为什么遍历 `num_set` 而不是 `nums`**
```python
# 推荐:遍历 num_set
for num in num_set: # 自动去重
# 不推荐:遍历 nums
for num in nums: # 可能有重复
```
**原因**
- `nums` 可能有重复元素
- 重复元素会导致重复计算
- `num_set` 自动去重
**细节3为什么需要 `longest` 变量?**
```python
longest = 0
for num in num_set:
current_len = ...
longest = max(longest, current_len)
```
**原因**
- 需要记录全局最大值
- 每次计算完一个序列后更新
- 最终返回 `longest`
### 边界条件分析
**边界1空数组**
```
输入nums = []
输出0
处理:
num_set = set()
for 循环不执行
longest = 0
```
**边界2单个元素**
```
输入nums = [1]
输出1
过程:
num_set = {1}
num=1: 1-1=0 不在集合中 → 起点
current_num=1, current_len=1
1+1=2 不在集合中 → 退出
longest = max(0, 1) = 1
输出1
```
**边界3全部重复**
```
输入nums = [1, 1, 1, 1]
输出1
过程:
num_set = {1}
num=1: 1-1=0 不在集合中 → 起点
current_num=1, current_len=1
1+1=2 不在集合中 → 退出
longest = 1
输出1
```
**边界4多个连续序列**
```
输入nums = [100, 4, 200, 1, 3, 2]
输出4
过程:
num_set = {100, 4, 200, 1, 3, 2}
num=100: 100-1=99 不在集合中 → 起点
current_num=100, current_len=1
101 不在集合中 → 退出
longest = 1
num=4: 4-1=3 在集合中 → 不是起点,跳过
num=200: 200-1=199 不在集合中 → 起点
current_num=200, current_len=1
201 不在集合中 → 退出
longest = max(1, 1) = 1
num=1: 1-1=0 不在集合中 → 起点
current_num=1, current_len=1
2 在集合中 → current_len=2
3 在集合中 → current_len=3
4 在集合中 → current_len=4
5 不在集合中 → 退出
longest = max(1, 4) = 4
num=3: 3-1=2 在集合中 → 不是起点,跳过
num=2: 2-1=1 在集合中 → 不是起点,跳过
输出4
```
**边界5负数**
```
输入nums = [-1, -2, 0, 1]
输出4
过程:
num_set = {-1, -2, 0, 1}
num=-1: -1-1=-2 在集合中 → 不是起点,跳过
num=-2: -2-1=-3 不在集合中 → 起点
current_num=-2, current_len=1
-1 在集合中 → current_len=2
0 在集合中 → current_len=3
1 在集合中 → current_len=4
2 不在集合中 → 退出
longest = 4
num=0: 0-1=-1 在集合中 → 不是起点,跳过
num=1: 1-1=0 在集合中 → 不是起点,跳过
输出4
```
### 复杂度分析(详细版)
**时间复杂度**
```
- 构建哈希表O(n)
- 外层循环O(n),遍历所有元素
- 内层 while总计 O(n)
- 每个元素只在 while 中被访问一次
- 因为只有起点才会进入 while
- 总计O(n) + O(n) + O(n) = O(n)
为什么是 O(n)
- 虽然有嵌套循环,但每个元素最多被访问 2 次
- 一次在哈希表中
- 一次在 while 循环中
- 总操作次数 = 2n = O(n)
```
**空间复杂度**
```
- 哈希表O(n),存储所有元素
- 变量O(1)
- 总计O(n)
```
---
## 图解过程
```
nums = [100, 4, 200, 1, 3, 2]
构建哈希表:
num_set = {100, 4, 200, 1, 3, 2}
遍历:
步骤1: num = 100
100-1=99 不在集合中 → 起点
序列:[100]
101 不在集合中 → 退出
longest = 1
步骤2: num = 4
4-1=3 在集合中 → 不是起点,跳过
步骤3: num = 200
200-1=199 不在集合中 → 起点
序列:[200]
201 不在集合中 → 退出
longest = 1
步骤4: num = 1
1-1=0 不在集合中 → 起点
序列:[1, 2, 3, 4]
5 不在集合中 → 退出
longest = 4
步骤5: num = 3
3-1=2 在集合中 → 不是起点,跳过
步骤6: num = 2
2-1=1 在集合中 → 不是起点,跳过
结果longest = 4
```
---
## 代码实现
```go
func longestConsecutive(nums []int) int {
// 构建哈希表
numSet := make(map[int]bool)
for _, num := range nums {
numSet[num] = true
}
longest := 0
// 遍历所有数字
for num := range numSet {
// 判断是否为序列起点
if !numSet[num-1] {
currentNum := num
current := 1
// 向后查找连续数字
for numSet[currentNum+1] {
currentNum++
current++
}
// 更新最大长度
if current > longest {
longest = current
}
}
}
return longest
}
```
**关键点**
1. 使用 map 实现 Set
2. 判断 `num-1` 是否存在
3. 只有起点才计算序列长度
---
## 执行过程演示
**输入**nums = [100, 4, 200, 1, 3, 2]
```
初始化numSet = {}, longest = 0
步骤1构建哈希表
numSet = {
100: true,
4: true,
200: true,
1: true,
3: true,
2: true
}
步骤2遍历哈希表
num=100:
100-1=99 不在 numSet 中 → 起点
currentNum=100, current=1
101 不在 numSet 中 → 退出
longest = max(0, 1) = 1
num=4:
4-1=3 在 numSet 中 → 不是起点,跳过
num=200:
200-1=199 不在 numSet 中 → 起点
currentNum=200, current=1
201 不在 numSet 中 → 退出
longest = max(1, 1) = 1
num=1:
1-1=0 不在 numSet 中 → 起点
currentNum=1, current=1
2 在 numSet 中 → currentNum=2, current=2
3 在 numSet 中 → currentNum=3, current=3
4 在 numSet 中 → currentNum=4, current=4
5 不在 numSet 中 → 退出
longest = max(1, 4) = 4
num=3:
3-1=2 在 numSet 中 → 不是起点,跳过
num=2:
2-1=1 在 numSet 中 → 不是起点,跳过
结果longest = 4
```
---
## 常见错误
### 错误1忘记去重
❌ **错误代码**
```go
// 直接遍历 nums可能有重复
for _, num := range nums {
// ...
}
```
✅ **正确代码**
```go
// 先构建 Set自动去重
numSet := make(map[int]bool)
for _, num := range nums {
numSet[num] = true
}
for num := range numSet {
// ...
}
```
**原因**
- `nums` 可能有重复元素
- 重复元素会导致重复计算
- 影响时间复杂度
---
### 错误2没有判断序列起点
❌ **错误代码**
```go
// 对每个数字都计算序列长度
for num := range numSet {
current := 1
for numSet[currentNum+1] {
// ...
}
}
```
✅ **正确代码**
```go
// 只对起点计算序列长度
for num := range numSet {
if !numSet[num-1] { // 判断是否为起点
// ...
}
}
```
**原因**
- 没有判断起点会重复计算
- 时间复杂度会变成 O(n²)
- 示例:[1,2,3,4] 会计算 4 次
---
### 错误3使用 list 而不是 set
❌ **错误代码**
```go
// 使用 list 查找
if contains(nums, num-1) { // O(n)
// ...
}
```
✅ **正确代码**
```go
// 使用 set 查找
if numSet[num-1] { // O(1)
// ...
}
```
**原因**
- `list` 查找是 O(n)
- `set` 查找是 O(1)
- 总复杂度会变成 O(n²)
---
## 进阶问题
### Q1: 如果需要返回最长序列本身?
```go
func longestConsecutiveSequence(nums []int) []int {
numSet := make(map[int]bool)
for _, num := range nums {
numSet[num] = true
}
longest := 0
start := 0 // 记录序列起点
for num := range numSet {
if !numSet[num-1] {
currentNum := num
current := 1
for numSet[currentNum+1] {
currentNum++
current++
}
if current > longest {
longest = current
start = num
}
}
}
// 构建结果
result := make([]int, longest)
for i := 0; i < longest; i++ {
result[i] = start + i
}
return result
}
```
---
### Q2: 如果数据量很大,如何优化内存?
**思路**使用布隆过滤器Bloom Filter
```go
// 布隆过滤器可以节省内存,但有误判率
// 适用于大数据场景
```
**注意**
- 布隆过滤器有误判率
- 需要根据场景调整参数
- 适合对准确性要求不高的场景
---
## P7 加分项
### 深度理解
- **哈希表的作用**O(1) 查找,实现 O(n) 时间复杂度
- **序列起点判断**:避免重复计算,保证 O(n) 时间
- **空间换时间**:用 O(n) 空间换取 O(n) 时间
### 实战扩展
- **大数据场景**:分布式计算、分片处理
- **内存优化**:布隆过滤器、位图
- **业务场景**:用户活跃度分析、时间窗口统计
### 变形题目
1. 最长连续序列(允许重复)
2. 最长等差序列
3. 最长递增子序列
---
## 总结
**核心要点**
1. **哈希表**O(1) 查找,快速判断元素是否存在
2. **序列起点判断**:避免重复计算,保证 O(n) 时间
3. **空间换时间**:用 O(n) 空间换取 O(n) 时间
**易错点**
- 忘记去重
- 没有判断序列起点
- 使用 list 而不是 set
**最优解法**:哈希表 + 序列起点判断,时间 O(n),空间 O(n)