LeetCode 128题:最长连续序列的哈希集合解法

发布时间:2026/10/27 19:32:10
LeetCode 128题:最长连续序列的哈希集合解法

1. 题目解析与核心思路

1.1 题目要求理解

LeetCode 128题"最长连续序列"的题目描述是这样的:给定一个未排序的整数数组nums,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。要求算法的时间复杂度为O(n)。

举个例子:

  • 输入:[100, 4, 200, 1, 3, 2]
  • 输出:4
  • 解释:最长连续序列是[1, 2, 3, 4],长度为4

这个题目看似简单,但有几个关键点需要注意:

  1. 数字连续但不需要在原数组中连续出现
  2. 要求O(n)时间复杂度,这意味着不能使用常规排序
  3. 需要处理重复数字和空数组的情况

1.2 暴力解法分析

最直观的解法是排序后遍历查找最长连续序列。例如:

  1. 先对数组进行排序
  2. 遍历排序后的数组,统计连续序列长度
  3. 记录最大长度

这种方法的时间复杂度是O(nlogn),因为排序需要O(nlogn)时间。虽然能通过部分测试用例,但不满足题目要求的O(n)时间复杂度。

1.3 哈希集合优化思路

要达到O(n)时间复杂度,我们需要避免排序操作。哈希集合(HashSet)的查找操作是O(1)的,这给了我们优化空间。核心思路是:

  1. 将所有数字存入哈希集合,实现O(1)时间的查找
  2. 对于每个数字,检查它是否是某个连续序列的起点(即num-1不在集合中)
  3. 如果是起点,则向后查找连续的数字,统计序列长度
  4. 记录最大长度

这种方法每个数字最多被访问两次(一次在检查起点时,一次在扩展序列时),因此时间复杂度是O(n)。

2. 详细实现与代码解析

2.1 Python实现代码

def longestConsecutive(nums): num_set = set(nums) max_length = 0 for num in num_set: # 检查是否是序列起点 if num - 1 not in num_set: current_num = num current_length = 1 # 向后扩展序列 while current_num + 1 in num_set: current_num += 1 current_length += 1 max_length = max(max_length, current_length) return max_length

2.2 代码逐行解析

  1. num_set = set(nums):将输入数组转换为集合,去重并实现O(1)查找
  2. max_length = 0:初始化最大长度为0
  3. for num in num_set::遍历集合中的每个数字
  4. if num - 1 not in num_set::检查当前数字是否是某个连续序列的起点
  5. current_num = num和current_length = 1:初始化当前序列
  6. while current_num + 1 in num_set::向后查找连续数字
  7. max_length = max(max_length, current_length):更新最大长度

2.3 时间复杂度分析

  • 创建集合:O(n)
  • 遍历集合:O(n)
  • 内部while循环:每个数字最多被访问两次(一次在外部循环,一次在内部扩展)
  • 总体时间复杂度:O(n)

空间复杂度是O(n),因为需要存储集合。

3. 边界条件与测试用例

3.1 常见边界情况

  1. 空数组:应返回0
  2. 所有数字相同:如[1,1,1],应返回1
  3. 无连续序列:如[1,3,5],应返回1
  4. 多个连续序列:如[1,2,3,100,101,102],应返回3
  5. 包含负数:如[0,-1],应返回2

3.2 测试用例设计

test_cases = [ ([100, 4, 200, 1, 3, 2], 4), ([], 0), ([1,1,1], 1), ([1,3,5], 1), ([1,2,3,100,101,102], 3), ([0,-1], 2), ([1,2,0,1], 3) ]

3.3 测试代码

for nums, expected in test_cases: result = longestConsecutive(nums) print(f"Input: {nums}, Output: {result}, Expected: {expected}") assert result == expected, f"Test failed for {nums}" print("All tests passed!")

4. 算法优化与变种

4.1 并查集解法

并查集(Union-Find)也可以解决这个问题,思路是:

  1. 初始化每个数字为独立的集合
  2. 对于每个数字,将其与num+1和num-1合并(如果存在)
  3. 最后统计最大的集合大小

虽然时间复杂度也是O(n),但实现起来比哈希集合方法复杂,且实际运行效率可能更低。

4.2 动态规划解法

动态规划思路是:

  1. 使用字典记录每个数字所在序列的长度
  2. 对于每个数字,检查其相邻数字是否在字典中
  3. 更新当前数字和边界数字的序列长度

这种方法同样能达到O(n)时间复杂度,但空间复杂度略高。

4.3 流式处理变种

如果数据是流式输入的(无法一次性获取所有数据),我们需要调整算法:

  1. 维护一个字典,记录每个数字所在序列的边界信息
  2. 对于新数字,检查其相邻数字是否已存在
  3. 合并相邻序列并更新边界信息

这种变种在实际工程中更有应用价值。

5. 实际应用场景

5.1 数据库连续ID检测

在数据库管理中,可能需要检测ID是否连续。例如:

  • 检测日志记录是否有缺失
  • 检查事务ID是否连续
  • 验证数据导入的完整性

5.2 时间序列分析

在分析时间序列数据时,可能需要找出连续的时间段:

  • 用户连续登录天数
  • 设备连续运行时长
  • 股票连续上涨/下跌天数

5.3 游戏开发应用

在游戏开发中,可能有以下应用:

  • 检测连续达成成就
  • 计算连续登录奖励
  • 分析玩家连续游戏时长

6. 常见错误与调试技巧

6.1 常见错误类型

  1. 未处理空数组情况
  2. 未考虑重复数字
  3. 错误计算序列长度(如只计算相邻数字)
  4. 时间复杂度不满足要求(如使用排序)
  5. 边界条件处理不当(如负数、大数)

6.2 调试技巧

  1. 使用小测试用例手动验证
  2. 打印中间变量(如当前序列)
  3. 检查集合操作是否正确
  4. 验证时间复杂度的理论分析
  5. 使用assert语句验证边界条件

6.3 性能优化建议

  1. 尽量减少集合操作次数
  2. 提前处理特殊情况(如空数组)
  3. 避免不必要的变量更新
  4. 考虑使用更高效的数据结构(如位图)
  5. 对于特定场景,可以牺牲空间换时间

7. 刷题策略与学习建议

7.1 同类题目推荐

  1. LeetCode 674. 最长连续递增序列
  2. LeetCode 300. 最长递增子序列
  3. LeetCode 1027. 最长等差数列
  4. LeetCode 1048. 最长字符串链

7.2 学习路线建议

  1. 先掌握基础数据结构(数组、哈希表)
  2. 理解时间复杂度的计算方法
  3. 练习多种解法(暴力、优化、特殊技巧)
  4. 总结常见问题模式
  5. 定期复习经典题目

7.3 面试准备技巧

  1. 明确问题要求(时间/空间复杂度)
  2. 先提出暴力解法,再优化
  3. 讨论边界条件和特殊情况
  4. 编写清晰易读的代码
  5. 准备测试用例并验证

在实际面试中遇到这道题时,建议按照以下步骤进行:

  1. 仔细阅读题目,确认理解正确
  2. 举例说明输入输出
  3. 提出暴力解法并分析复杂度
  4. 思考优化方向(哈希表、双指针等)
  5. 实现优化解法并验证复杂度
  6. 讨论边界条件和测试用例
  7. 如有时间,可以提及其他解法或变种