LeetCode 128. 最长连续序列(O(n)时间复杂度详解)

本文针对 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])为例,模拟代码运行过程,直观理解逻辑:

  1. num_set = {100,4,200,1,3,2},max_length = 0;

  2. 遍历 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 中,跳过;

  3. 循环结束,返回 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) 复杂度」的核心设计思路,为类似的数组连续问题提供解题参考。

相关推荐
LiLiYuan.2 小时前
【如何理解递归链表?】
数据结构·链表
Frostnova丶2 小时前
LeetCode 1009 & 476 数字的补数
算法·leetcode
CppBlock2 小时前
HPX vs TBB vs OpenMP:并行任务模型对比
c++·算法
17(无规则自律)2 小时前
Leetcode第六题:用 C++ 解决三数之和
c++·算法·leetcode
美式请加冰2 小时前
哈希表的介绍和使用
数据结构·散列表
进击的小头2 小时前
第4篇:二阶系统的时域响应分析
python·算法
wengqidaifeng2 小时前
备战蓝桥杯----C/C++组 (一)所需C++基础知识(上)
c语言·数据结构·c++·蓝桥杯
tankeven2 小时前
HJ126 小红的正整数计数
c++·算法
0 0 02 小时前
CCF-CSP 37-2 机器人饲养指南(apple)【C++】考点:完全背包问题
开发语言·c++·算法