本文针对 LeetCode 128 题「最长连续序列」进行详细技术解析,核心聚焦 O(n) 时间复杂度 的算法设计与实现,拆解问题本质、推导解题思路、分析代码细节,并结合示例验证逻辑,帮助开发者深入理解解题核心,规避常见误区。
题目核心要求:给定一个未排序的整数数组 nums,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度,且必须设计时间复杂度为 O(n) 的算法。
一、问题分析
1.1 题目核心痛点
题干有两个关键约束,也是解题的核心难点:
-
数组未排序:无法直接通过遍历相邻元素找连续序列,常规的排序+遍历(时间复杂度 O(n log n))不符合要求;
-
时间复杂度必须为 O(n):意味着算法只能对数组进行常数次遍历,不能使用嵌套循环(如暴力枚举每个元素,再依次查找后续连续数字,时间复杂度 O(n²) 会超时)。
1.2 示例解析(辅助理解问题)
结合题干示例,明确「数字连续序列」的定义:序列中数字依次递增 1,不要求在原数组中相邻,仅要求数字存在。
-
示例 1:nums = [100,4,200,1,3,2],数字连续序列有 [100]、[4,3,2,1]、[200],最长为 4;
-
示例 2:nums = [0,3,7,2,5,8,4,6,0,1],最长连续序列为 [0,1,2,3,4,5,6,7,8],长度为 9;
-
示例 3:nums = [1,0,1,2],去重后连续序列为 [0,1,2],长度为 3(重复元素不影响连续序列判断)。
1.3 关键观察(解题突破口)
要实现 O(n) 时间复杂度,核心是「快速判断一个数字的前序/后序数字是否存在」,而哈希表(Set/Map)的查找操作时间复杂度为 O(1),恰好满足需求。
进一步观察:一个连续序列的「起点」满足一个关键特征------起点数字的前一个数字(num-1)不在数组中。例如序列 [1,2,3,4],起点是 1,因为 0 不在数组中;而 2、3、4 都不是起点,因为它们的前一个数字(1、2、3)都在数组中。
基于此,我们只需遍历数组中所有「起点」,并从起点开始,依次查找后续连续数字(num+1、num+2...),统计最长序列长度,即可高效解决问题。
二、解题思路(O(n) 时间复杂度)
整体思路分为 3 步,核心依托哈希表实现快速查找,确保每一步操作都是 O(1) 时间,整体遍历次数为常数次,最终达到 O(n) 复杂度。
步骤 1:去重并存储到哈希表
数组中可能存在重复元素(如示例 3),重复元素对连续序列长度无影响,反而会增加遍历冗余。因此,先将数组元素存入 Set 中,实现去重,同时利用 Set 的 O(1) 查找特性。
注意:Set 存储的是数组中所有不重复的元素,后续所有查找操作都基于这个 Set,避免重复处理。
步骤 2:遍历哈希表,寻找序列起点
遍历 Set 中的每一个数字 num,判断 num-1 是否在 Set 中:
-
若 num-1 不在 Set 中:说明 num 是一个连续序列的起点,开始统计该序列的长度;
-
若 num-1 在 Set 中:说明 num 不是起点,跳过(避免重复统计同一个序列,比如 2 的前一个 1 存在,就不用从 2 开始统计,因为从 1 开始统计时会包含 2)。
步骤 3:统计每个起点对应的序列长度,更新最大值
对于每个确定的起点 num,依次查找 num+1、num+2、... 直到某个数字不在 Set 中,统计该序列的长度。每次统计后,与当前最长长度对比,更新最大值。
关键优化:每个数字只会被遍历一次(每个序列的起点被遍历一次,后续数字仅在统计序列长度时被查找一次,且不会重复统计),因此整体时间复杂度仍为 O(n)。
三、代码实现(Python)
按照上述思路,实现 Python 代码,严格遵循 LeetCode 解题模板,注释详细,便于理解每一步逻辑,同时确保时间复杂度 O(n)、空间复杂度 O(n)(哈希表存储元素)。
python
class Solution:
def longestConsecutive(self, nums: List[int]) -> int:
# 步骤1:将数组元素存入Set,去重且支持O(1)查找
num_set = set(nums)
max_length = 0 # 存储最长连续序列长度
# 步骤2:遍历Set,寻找每个序列的起点
for num in num_set:
# 判断当前数字是否是序列起点(num-1不在Set中)
if num - 1 not in num_set:
current_num = num # 当前序列的起点
current_length = 1 # 当前序列的长度(至少包含起点本身)
# 步骤3:从起点开始,统计连续序列长度
while current_num + 1 in num_set:
current_num += 1
current_length += 1
# 更新最长序列长度
max_length = max(max_length, current_length)
return max_length
四、代码细节解析
4.1 哈希表选择(Set vs Map)
本题使用 Set 而非 Map 的原因:我们只需要判断「数字是否存在」,不需要存储额外的键值对。Set 的查找、插入操作都是 O(1) 时间,且去重功能刚好满足需求,比 Map 更简洁、高效。
4.2 核心逻辑拆解
-
去重操作:set(nums) 自动去除数组中的重复元素,例如示例 3 中的 [1,0,1,2] 会变成 {0,1,2},避免重复处理同一个数字;
-
起点判断:if num - 1 not in num_set,确保每个序列只从起点开始统计,避免重复遍历(比如 1、2、3、4,只从 1 开始统计,2、3、4 都会被跳过);
-
序列长度统计:while 循环查找 current_num + 1 是否存在,存在则长度加 1,直到找不到为止,此时得到当前序列的完整长度;
-
最大值更新:每次统计完一个序列,用 max 函数更新 max_length,确保最终返回的是最长序列长度。
4.3 边界情况处理
本题的边界情况需重点关注,避免代码报错或逻辑错误:
-
边界 1:nums 为空(nums.length = 0),此时返回 0(Set 为空,循环不执行,max_length 保持初始值 0);
-
边界 2:nums 只有一个元素(如 [5]),此时返回 1(只有一个起点,序列长度为 1);
-
边界 3:nums 有重复元素(如 [1,1,2,2]),Set 去重后为 {1,2},序列长度为 2,正确;
-
边界 4:负数序列(如 [-3,-2,-1,0]),逻辑同样适用,起点为 -3,序列长度为 4。
五、复杂度分析(关键验证 O(n))
5.1 时间复杂度
整体时间复杂度为 O(n),原因如下:
-
将数组存入 Set:O(n)(遍历数组一次,插入 Set 的操作是 O(1) 每步);
-
遍历 Set 并统计序列长度:每个数字只会被访问两次(一次是在遍历 Set 时,一次是在统计序列长度的 while 循环中),整体仍为 O(n);
-
所有操作均为常数次遍历,无嵌套循环,因此总时间复杂度为 O(n),满足题目要求。
5.2 空间复杂度
空间复杂度为 O(n),因为 Set 存储了数组中所有不重复的元素,最坏情况下(数组无重复元素),Set 的大小等于数组长度 n。
六、示例验证(代码运行过程)
以示例 1(nums = [100,4,200,1,3,2])为例,模拟代码运行过程,直观理解逻辑:
-
num_set = {100,4,200,1,3,2},max_length = 0;
-
遍历 num_set 中的每个元素:
-
num = 100:100-1=99 不在 Set 中,是起点;current_num=100,current_length=1;101 不在 Set,max_length 更新为 1;
-
num = 4:4-1=3 在 Set 中,不是起点,跳过;
-
num = 200:200-1=199 不在 Set 中,是起点;current_length=1,max_length 仍为 1;
-
num = 1:1-1=0 不在 Set 中,是起点;current_num=1,current_length=1;查找 2(存在),current_length=2;查找 3(存在),current_length=3;查找 4(存在),current_length=4;查找 5(不存在),max_length 更新为 4;
-
num = 3:3-1=2 在 Set 中,跳过;
-
num = 2:2-1=1 在 Set 中,跳过;
-
-
循环结束,返回 max_length=4,与示例输出一致。
七、常见误区与优化建议
7.1 常见误区
-
误区 1:使用排序+遍历(O(n log n) 复杂度),虽然代码简单,但不符合 O(n) 时间要求,会在大数据量(n=10⁵)时超时;
-
误区 2:暴力枚举每个元素,再依次查找 num+1、num+2...(O(n²) 复杂度),同样会超时;
-
误区 3:未去重,导致重复统计同一个序列(如示例 3),虽然最终结果可能正确,但会增加冗余操作,降低效率。
7.2 优化建议
-
优化 1:若数组规模极大(n=10⁵),可直接使用 Set,无需额外处理,Python 的 Set 底层基于哈希表,性能稳定;
-
优化 2:若需节省空间,可尝试原地去重(但会破坏原数组,且时间复杂度仍为 O(n),不如 Set 简洁);
-
优化 3:对于负数、零的处理,代码无需额外修改,Set 可正常存储和查找,逻辑通用。
八、总结
本题的核心是「利用哈希表实现 O(1) 查找,通过寻找序列起点避免重复统计」,最终实现 O(n) 时间复杂度。解题关键在于抓住「序列起点的特征」,减少冗余遍历,同时利用 Set 的去重和快速查找特性,平衡时间和空间复杂度。
代码逻辑简洁易懂,边界情况处理全面,适用于题目给定的所有约束条件(n 最大 10⁵)。通过本文的解析,可掌握哈希表在「快速查找、去重」场景下的应用,同时理解「O(n) 复杂度」的核心设计思路,为类似的数组连续问题提供解题参考。