128. 最长连续序列 (Medium)
专题归类: 01-哈希表 · 03-数组 LeetCode 链接: https://leetcode.cn/problems/longest-consecutive-sequence/
在线做题: 写 Python 代码并运行 · Java 跳转 LeetCode
题目描述
给定一个未排序的整数数组 nums,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
示例:
输入:nums = [100, 4, 200, 1, 3, 2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4],长度为 4
补充说明:
- 数组长度范围:
0 <= nums.length <= 10^5 - 数值范围:
-10^9 <= nums[i] <= 10^9
题目详细分析
数据范围含义:
- 数组最长 10^5,允许 O(n log n) 乃至 O(n^2) 的朴素解法?不行,题目明确要求 O(n),这是一个强约束,直接排除了排序法(O(n log n))和暴力法(O(n
- 数值范围 ±10
- 数组长度可以为 0,此时最长连续序列长度为 0。
输入输出特征:
- 输入完全无序,不能指望有任何顺序信息。
- 输出是一个整数,表示最长连续序列的长度。
- 注意”序列”在这里指数值上连续(如 1,2,3,4),而不是在原数组中的位置连续。
核心约束:
- O(n) 时间复杂度是最大的挑战。这要求每个元素的处理时间必须是 O(1) 摊销。
- 不能排序(排序就是 O(n log n))。
隐藏条件:
- 重复元素不影响结果——
[1, 2, 2, 3]的最长连续序列是 3 而非 4,因为重复的 2 不计入长度。 - 题目不要求返回具体的序列,只需要长度,这降低了复杂度。
小白版直白理解
想象你在整理一堆彩票号码。你想知道最长的”连号”有多长——比如你手上有 1, 3, 2, 4, 100, 200,那最长的连号就是 1-2-3-4,长度 4。
笨办法(排序法): 把号码从小到大排好,然后看哪些是连续的。但排序就要花不少时间,不符合要求。
聪明办法(哈希集合法): 先把所有号码写进一个本子(哈希集合),然后一个个检查。
关键窍门是:只从每个”连续串”的开头开始数。比如数字 100,你要先看 99 在不在本子里?不在!那 100 就是一个连续串的开头,从它往后数 101、102……看能连续多少个。
而数字 3,你要先看 2 在不在本子里?在!说明 3 不是开头,跳过它不管。这样就避免了重复计算——每个数字最多被访问两次(一次检查是否是开头,一次作为开头往后延伸)。
解题思路
思路一:哈希集合 + 起点查找(推荐)
关键洞察: 如果不做优化,对每个元素都往后查找连续序列,最坏情况 O(n^2)。例如 [1, 2, 3, 4, 5]——每个元素都会往后找一遍。
核心优化:只从序列的起点开始查找。 如何判断一个数字是起点?检查 num - 1 是否在集合中。如果不在,说明 num 是一个连续序列的第一个元素;如果在,说明 num 不是起点,跳过它。
这个优化的美妙之处在于:每个元素最多被”往后延伸”一次(只有当它是起点时),总体 O(n)。
def longestConsecutive(nums):
"""
哈希集合 + 起点查找
只从序列的起点开始向后延伸
步骤:
1. 将所有元素放入集合(去重)
2. 遍历集合,只对 num-1 不在集合中的元素(即起点)进行延伸
3. while 循环检查 current+1 是否在集合中
4. 更新全局最大长度
"""
num_set = set(nums)
longest = 0
for num in num_set:
# 只从序列起点开始查找
if num - 1 not in num_set:
current = num
length = 1
# 向后延伸,查找连续数字
while current + 1 in num_set:
current += 1
length += 1
longest = max(longest, length)
return longest思路二:排序 + 扫描(不满足 O(n),但思路简单)
先排序,再遍历一次。遇到连续的数字计数加 1,遇到不连续的重置计数,遇到重复的跳过。
虽然清晰易懂,但排序本身就是 O(n log n),不满足题目要求。
def longestConsecutive_sort(nums):
if not nums:
return 0
nums.sort()
longest = 1
cur_len = 1
for i in range(1, len(nums)):
if nums[i] == nums[i-1]:
# 重复元素,跳过
continue
elif nums[i] == nums[i-1] + 1:
# 连续,延长
cur_len += 1
else:
# 断开了,重置
longest = max(longest, cur_len)
cur_len = 1
return max(longest, cur_len)思路三:并查集(Union-Find,进阶)
可以用并查集将连续的数字合并到同一个集合中,每个集合维护一个 size。最终找最大的 size。
虽然有趣,但实现比哈希集合法复杂,在面试中不推荐作为首选。
易错点
- 重复元素:
nums = [0, 0, 1, 2],最长的连续序列是 [0, 1, 2] 长度 3,不是 4。必须用 set 去重,否则重复的 0 会被多算。 - 空数组:
nums = []直接返回 0。如果没有空值检查,set()会在 for 循环中正常处理(不进入循环,返回 0),但排序法需要显式处理。 - 遍历 set 而非原数组: 在 for 循环中应该遍历
num_set而不是nums。这样可以减少重复元素带来的不必要检查。 - 负数处理:
num - 1 not in num_set对负数同样有效,不需要特殊处理。 - 溢出不需担心: Python 的 int 是任意精度的,±10^9 范围内没问题。
框架提炼
哈希集合 + 起点查找模板:
这类问题的核心模式是:跳过非起点,只从起点延伸。
def find_consecutive(nums):
"""
哈希集合 + 起点查找 通用模板
"""
# 1. 去重
elements = set(nums)
max_count = 0
for x in elements:
# 2. 判断是否为起点(前驱不在集合中)
if x - 1 not in elements:
# 3. 从起点向后延伸
current = x
count = 0
while current in elements: # 或 current + step in elements
count += 1
current += 1 # 或 current += step
# 4. 更新最优解
max_count = max(max_count, count)
return max_count适用场景特征:
- 需要在无序数组中找连续/递增的序列
- 有 O(n) 的时间复杂度要求
- 通过”跳过非起点”避免 O(n^2) 的重复计算
关联题目
- 1-两数之和 — 同样使用哈希表来实现 O(1) 查找,但两数之和是查找 target - num,本题是查找 num + 1(向后延伸)
- 49-字母异位词分组 — 同样是哈希表 + 数组类问题,但”分组”用的是哈希表不同的功能维度
- 674-最长连续递增序列 — 本题的简化版,但要求在数组中的位置也连续(子数组),可以用一次遍历解决,不需要哈希表