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

14 KiB
Raw Blame History

最长连续序列 (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]。

思路推导

暴力解法分析

最直观的思路:排序后遍历,找到最长的连续序列。

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

num_set = set(nums)

for num in nums:
    # 检查 num+1 是否在集合中
    if num + 1 in num_set:
        # 继续检查 num+2, num+3, ...

为什么这样思考?

  • 哈希表查找O(1)
  • 可以快速判断一个数是否存在
  • 不需要排序

优化思考 - 第二步:寻找序列起点

关键优化:如何避免重复计算同一个序列?

观察:只有当一个数是序列的起点时,才需要计算

# 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构建哈希表

num_set = set(nums)

作用

  • 快速判断元素是否存在
  • O(1) 时间复杂度

步骤2遍历所有数字

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

# 推荐:使用 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

# 推荐:遍历 num_set
for num in num_set:  # 自动去重

# 不推荐:遍历 nums
for num in nums:  # 可能有重复

原因

  • nums 可能有重复元素
  • 重复元素会导致重复计算
  • num_set 自动去重

细节3为什么需要 longest 变量?

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

代码实现

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忘记去重

错误代码

// 直接遍历 nums可能有重复
for _, num := range nums {
    // ...
}

正确代码

// 先构建 Set自动去重
numSet := make(map[int]bool)
for _, num := range nums {
    numSet[num] = true
}

for num := range numSet {
    // ...
}

原因

  • nums 可能有重复元素
  • 重复元素会导致重复计算
  • 影响时间复杂度

错误2没有判断序列起点

错误代码

// 对每个数字都计算序列长度
for num := range numSet {
    current := 1
    for numSet[currentNum+1] {
        // ...
    }
}

正确代码

// 只对起点计算序列长度
for num := range numSet {
    if !numSet[num-1] {  // 判断是否为起点
        // ...
    }
}

原因

  • 没有判断起点会重复计算
  • 时间复杂度会变成 O(n²)
  • 示例:[1,2,3,4] 会计算 4 次

错误3使用 list 而不是 set

错误代码

// 使用 list 查找
if contains(nums, num-1) {  // O(n)
    // ...
}

正确代码

// 使用 set 查找
if numSet[num-1] {  // O(1)
    // ...
}

原因

  • list 查找是 O(n)
  • set 查找是 O(1)
  • 总复杂度会变成 O(n²)

进阶问题

Q1: 如果需要返回最长序列本身?

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

// 布隆过滤器可以节省内存,但有误判率
// 适用于大数据场景

注意

  • 布隆过滤器有误判率
  • 需要根据场景调整参数
  • 适合对准确性要求不高的场景

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)