LeetCode 128题:最长连续序列的哈希集合解法
1. 题目解析与核心思路
1.1 题目要求理解
LeetCode 128题"最长连续序列"的题目描述是这样的:给定一个未排序的整数数组nums,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。要求算法的时间复杂度为O(n)。
举个例子:
- 输入:[100, 4, 200, 1, 3, 2]
- 输出:4
- 解释:最长连续序列是[1, 2, 3, 4],长度为4
这个题目看似简单,但有几个关键点需要注意:
- 数字连续但不需要在原数组中连续出现
- 要求O(n)时间复杂度,这意味着不能使用常规排序
- 需要处理重复数字和空数组的情况
1.2 暴力解法分析
最直观的解法是排序后遍历查找最长连续序列。例如:
- 先对数组进行排序
- 遍历排序后的数组,统计连续序列长度
- 记录最大长度
这种方法的时间复杂度是O(nlogn),因为排序需要O(nlogn)时间。虽然能通过部分测试用例,但不满足题目要求的O(n)时间复杂度。
1.3 哈希集合优化思路
要达到O(n)时间复杂度,我们需要避免排序操作。哈希集合(HashSet)的查找操作是O(1)的,这给了我们优化空间。核心思路是:
- 将所有数字存入哈希集合,实现O(1)时间的查找
- 对于每个数字,检查它是否是某个连续序列的起点(即num-1不在集合中)
- 如果是起点,则向后查找连续的数字,统计序列长度
- 记录最大长度
这种方法每个数字最多被访问两次(一次在检查起点时,一次在扩展序列时),因此时间复杂度是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_length2.2 代码逐行解析
num_set = set(nums):将输入数组转换为集合,去重并实现O(1)查找max_length = 0:初始化最大长度为0for num in num_set::遍历集合中的每个数字if num - 1 not in num_set::检查当前数字是否是某个连续序列的起点current_num = num和current_length = 1:初始化当前序列while current_num + 1 in num_set::向后查找连续数字max_length = max(max_length, current_length):更新最大长度
2.3 时间复杂度分析
- 创建集合:O(n)
- 遍历集合:O(n)
- 内部while循环:每个数字最多被访问两次(一次在外部循环,一次在内部扩展)
- 总体时间复杂度:O(n)
空间复杂度是O(n),因为需要存储集合。
3. 边界条件与测试用例
3.1 常见边界情况
- 空数组:应返回0
- 所有数字相同:如[1,1,1],应返回1
- 无连续序列:如[1,3,5],应返回1
- 多个连续序列:如[1,2,3,100,101,102],应返回3
- 包含负数:如[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)也可以解决这个问题,思路是:
- 初始化每个数字为独立的集合
- 对于每个数字,将其与num+1和num-1合并(如果存在)
- 最后统计最大的集合大小
虽然时间复杂度也是O(n),但实现起来比哈希集合方法复杂,且实际运行效率可能更低。
4.2 动态规划解法
动态规划思路是:
- 使用字典记录每个数字所在序列的长度
- 对于每个数字,检查其相邻数字是否在字典中
- 更新当前数字和边界数字的序列长度
这种方法同样能达到O(n)时间复杂度,但空间复杂度略高。
4.3 流式处理变种
如果数据是流式输入的(无法一次性获取所有数据),我们需要调整算法:
- 维护一个字典,记录每个数字所在序列的边界信息
- 对于新数字,检查其相邻数字是否已存在
- 合并相邻序列并更新边界信息
这种变种在实际工程中更有应用价值。
5. 实际应用场景
5.1 数据库连续ID检测
在数据库管理中,可能需要检测ID是否连续。例如:
- 检测日志记录是否有缺失
- 检查事务ID是否连续
- 验证数据导入的完整性
5.2 时间序列分析
在分析时间序列数据时,可能需要找出连续的时间段:
- 用户连续登录天数
- 设备连续运行时长
- 股票连续上涨/下跌天数
5.3 游戏开发应用
在游戏开发中,可能有以下应用:
- 检测连续达成成就
- 计算连续登录奖励
- 分析玩家连续游戏时长
6. 常见错误与调试技巧
6.1 常见错误类型
- 未处理空数组情况
- 未考虑重复数字
- 错误计算序列长度(如只计算相邻数字)
- 时间复杂度不满足要求(如使用排序)
- 边界条件处理不当(如负数、大数)
6.2 调试技巧
- 使用小测试用例手动验证
- 打印中间变量(如当前序列)
- 检查集合操作是否正确
- 验证时间复杂度的理论分析
- 使用assert语句验证边界条件
6.3 性能优化建议
- 尽量减少集合操作次数
- 提前处理特殊情况(如空数组)
- 避免不必要的变量更新
- 考虑使用更高效的数据结构(如位图)
- 对于特定场景,可以牺牲空间换时间
7. 刷题策略与学习建议
7.1 同类题目推荐
- LeetCode 674. 最长连续递增序列
- LeetCode 300. 最长递增子序列
- LeetCode 1027. 最长等差数列
- LeetCode 1048. 最长字符串链
7.2 学习路线建议
- 先掌握基础数据结构(数组、哈希表)
- 理解时间复杂度的计算方法
- 练习多种解法(暴力、优化、特殊技巧)
- 总结常见问题模式
- 定期复习经典题目
7.3 面试准备技巧
- 明确问题要求(时间/空间复杂度)
- 先提出暴力解法,再优化
- 讨论边界条件和特殊情况
- 编写清晰易读的代码
- 准备测试用例并验证
在实际面试中遇到这道题时,建议按照以下步骤进行:
- 仔细阅读题目,确认理解正确
- 举例说明输入输出
- 提出暴力解法并分析复杂度
- 思考优化方向(哈希表、双指针等)
- 实现优化解法并验证复杂度
- 讨论边界条件和测试用例
- 如有时间,可以提及其他解法或变种