128. 最长连续序列(Longest Consecutive Sequence)

难度:中等 | 主题:哈希表 / 集合 / 数组

题目

给定一个未排序的整数数组 nums,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。要求时间复杂度 O(n)。

示例

输入:nums = [100,4,200,1,3,2]

输出:4

解释:最长连续序列为 [1,2,3,4],长度 4


思路

先讲个故事:电影院找座位

你走进电影院,票上没写座位号。你从入口开始看——但是电影院的座位号是打乱的。

你想知道:最长的连续空座排有多长?

最笨的办法:找到最左边座位号,然后一个个往后数连续的座位,数完一个排再找下一个起点。

但有个问题——你可能会把同一排数好几遍。

聪明办法:只从每排的最左边座位开始数。如果一个座位的左手边没人(没有前驱),它就是排头。从排头往后数,保证每排只数一次。


引导式推导:从排序到集合

第 1 层(暴力):排序后遍历,相邻差 1 就累加。O(n log n) —— 但题目要求 O(n)。

第 2 层(哈希集 + 起点优化)

把所有数丢进哈希集合。遍历每个数,只有 num - 1 不在集合中(说明是连续段的开头)才启动内循环往后数。


graph LR

    A["集合: {100,4,200,1,3,2}"] --> B{"num=100<br/>99在吗?"}

    B -->|不在→起点| C["数: 100→101(不在) 长度1"]

    B --> D{"num=4<br/>3在吗?"}

    D -->|在→跳过| E{"num=1<br/>0在吗?"}

    E -->|不在→起点| F["数: 1→2→3→4→5(不在) 长度4"]

关键洞察:只有起点触发的内循环才真正干活,非起点直接跳过。 看似两层循环,但每个元素最多被内层访问一次,总 O(n)。

为什么不是 O(n²)? 内层 while 只有序列起点才触发,而每个元素一旦被 while 消费就标记为已访问(不会再次进入),均摊到每个元素 O(1)。

做法时间空间满足 O(n)
集合 + 起点优化O(n)O(n)
排序 + 扫描O(n log n)O(log n)

代码

 
def longestConsecutive(self, nums):
 
    num_set = set(nums)                    # 哈希集合:O(1) 查找 + 自动去重
 
    max_count = 0                          # 全局最长连续序列
 
    for num in num_set:                    # 遍历集合(去重后,减少无谓检查)
 
        if num - 1 not in num_set:         # 核心判断:无前驱 → 序列起点
 
            current = num
 
            count = 1
 
            while current + 1 in num_set:  # 向后扩展连续序列
 
                current += 1
 
                count += 1
 
            max_count = max(max_count, count)
 
    return max_count
 

复杂度

指标解释
时间O(n)建集合 O(n),内层 while 均摊 O(1)
空间O(n)哈希集合存全部元素
排序法O(n log n)不满足题目要求

实战考量

频率分析

出现在:字节/美团/阿里 常考,约 40% 的会拿这道题测试你能不能突破”排序惯性”想到哈希优化。

延伸思考

Q:为什么排序不行?

A:排序 O(n log n) 不满足题目明确的 O(n) 要求。关键就是能否跳出排序思维定势。

Q:内层 while 真的是 O(n) 吗?

A:是。每个元素最多被 while 吃一次——只有起点才触发内循环,非起点直接跳过。均摊下来每个元素 O(1)。

Q:如果要求返回连续序列本身,而不仅是长度呢?

A:同时记录起点和终点,while 结束时用 nums[start:end] 切片保存,更新最大序列时一并更新结果数组。

Q:如果数据是数据流(streaming),不能一次性建集合呢?

A:可以用并查集(Union-Find)维护每个数的连续区间边界,union(x, x+1) 动态合并,根节点维护区间长度。

Q:重复元素会影响结果吗?

A:不影响。集合自动去重,连续序列计数只看值不看出现次数。

易错点

  • 遍历 num_set 而非原数组——减少重复检查,但遍历原数组也可以

  • 忘记 max_count 初始为 0,空数组时返回 0

  • 内层 while 结束后才更新 max_count,不是每步都更新


生活类比

找连续座位 → 找序列起点

想象你在一排排打乱的座位中找最长连续空座。你会从每排最左边开始数,而不是每个座位都从头数一遍。

哈希集合就是让你 O(1) 知道”左边有没有人”的座位表。

用两个字概括:排头只数一次。


相关题目

题目关系
560和为K的子数组同为哈希表加速查找的变体
219存在重复元素II哈希表 + 索引约束
349两个数组的交集集合的另一应用

→ 返回题单:LeetCode学习路线图 > 一、数组与哈希(含双指针、矩阵)

速记卡(面试闪卡)

Q1:一句话讲清「128. 最长连续序列(Longest Consecutive Sequence)」到底是什么?

A:把数组丢进哈希集,只从没前驱的序列起点往后数,均摊 O(n) 求最长连续段。

Q2:一、题目:找最长连续数字段 —— 怎么理解?

A:像在一堆打乱的座位号里,找最长连续空座排的长度。给未排序数组,求数字连续的最长序列长度,要求 O(n)(Longest Consecutive Sequence)。

Q3:二、思路:排头只数一次 —— 怎么理解?

A:像电影院找最长连续空座:只从每排最左边(没有前驱的座位)开始数,保证每排只数一遍(Start-point optimization)。哈希集 O(1) 判断”左边有人吗”,非起点直接跳过。

Q4:三、代码:起点触发内循环 —— 怎么理解?

A:像查座位表:遍历集合,若 num-1 不在就当排头,while 往后数到断;每个元素最多被数一次,均摊 O(1)(Hash set scan)。

Q5:四、复杂度与实战:跳出排序惯性 —— 怎么理解?

A:时间 O(n)、空间 O(n),排序法 O(n log n) 不达标。字节/美团/阿里常考,约 40% 用来测你能否跳出”先排序”的思维定势(Sorting bias)。

Q6:核心速记主线有哪些?

  • 思路:哈希集去重,只从序列起点(num-1 不在)往后数

  • 复杂度:时间 O(n) 空间 O(n),排序法不达标

  • 为什么非 O(n²):内层 while 均摊 O(1),每元素消费一次

  • 实战:大厂常考,考的是跳出排序惯性想哈希优化

  • 延伸:返回序列本身记起止点;流式用并查集

口诀

A:连续序列求最长,

排序惯性先扔光;

集合查左无前驱,

排头只数一遍香。

相关链接