最长连续序列 图解题解
无序数组里找最长连续整数段,不排序能做到 O(n) 吗?哈希集合加一个「只从起点起跳」的技巧,就行。
就像在一堆散乱的地图碎片里找最长的连续路段:先把所有碎片编号丢进一个「速查箱」(哈希集合),再找规律——只有当一个碎片的「前一段」不在箱子里时,它才是起点。从每个起点出发,一格格查下一片在不在,在就继续,不在就停,记下这段长度。跳过中间碎片,只从起点起跳,所以每个碎片最多被碰一次。
这道题到底在问什么
- nums
- [100,4,200,1,3,2,5,6]
- 输出
- 6
最优解:为什么这么做
一句话答案:LeetCode 128 最长连续序列的 O(n) 解法是哈希集合加「只从起点数」:先把所有数放进集合获得 O(1) 查询,然后只对满足 x - 1 不在集合里的数 x(即某段连续序列的起点)向上逐个数 x+1、x+2 是否存在。每个数最多被访问两次,时间 O(n)、空间 O(n),绕开了排序的 O(n log n)。
题目要什么,难点藏在哪
给一个无序整数数组,求「数值上连续」的最长一段有多长——比如 [100,4,200,1,3,2,5,6] 里最长的是 1、2、3、4、5、6,答案 6。元素在原数组里的位置无关紧要,只看数值。真正的难点是题目附加的硬要求:时间复杂度必须 O(n),这一刀直接把「先排序再扫一遍」的 O(n log n) 常规做法排除在正解之外。
为什么不能排序,暴力又卡在哪
排序做法思路简单:排完序连续的数就挨在一起,扫一遍统计最长连击即可,但排序本身 O(n log n),不满足题目要求。另一个直觉是对每个数 x 都试着往上数:x+1 在不在、x+2 在不在……判断「在不在」若靠遍历数组是 O(n) 一次,整体最坏 O(n²);就算用哈希集合把查询降到 O(1),「每个数都往上数一遍」在数组本身就是一长串连续数时,仍会把同一段序列重复数 n 遍,还是 O(n²)。
关键观察:只从每段序列的起点开始数
省掉重复的办法是给「往上数」加一个准入条件:只有当 x - 1 不在集合里时才从 x 起步。x - 1 不存在,说明 x 是某段连续序列的最左端(起点);反过来,只要 x - 1 存在,x 就处在别人的序列中段,从它数出来的一定是条被截短的重复子段,不数也罢。
这个小判断改变了整体的账:每段连续序列只会被它唯一的起点完整数一遍,序列内部的其他数只在准入判断时被看一眼。所有段加起来,每个元素最多被访问常数次,总时间稳稳落在 O(n)。
哈希集合在这里干了两件事
第一件是去重:set(nums) 把重复元素合并,重复的数既不该让序列变长,也会干扰起点判断,入集合时顺手解决。第二件是 O(1) 查询:无论是「x - 1 在不在」的起点判断,还是往上数时「cur + 1 在不在」的续链判断,都是一次哈希查找。整套算法就是「一次建集合 + 对每个集合元素做常数次查询」,没有任何排序或嵌套扫描。
复杂度怎么数出来,哪里容易翻车
时间 O(n):建集合 O(n);遍历集合时,非起点的数只花一次判断,起点的数把自己那段数完,每个元素作为「被数到的对象」恰好一次,两项相加仍是线性。空间 O(n):集合存全部去重后的数。
最容易翻车的一处是漏掉「只从起点数」这个限制——代码能跑、答案也对,但复杂度退化成 O(n²),大数据量直接超时,面试官追问复杂度时也答不上来。其次是遍历对象:第二遍循环应遍历去重后的集合而不是原数组,否则重复元素会让同一个起点被反复数。
▶ 动画逐步走查(共 28 步)——想跟着动画一帧帧对照就展开
- 3核心:先把所有数放进哈希集合(去重 + O(1) 查询)。再只从「起点」起步往上数——一个数 x 是起点当且仅当 x-1 不在集合里。这样每个数只会被数到一次,总共 O(n)。
- 4把 100 放进哈希集合第一遍:把 nums[0] = 100 放进哈希集合(橙色)。集合会自动去重,并支持 O(1) 判断某个数在不在。放完所有数后再开始数连续。
- 5把 4 放进哈希集合第一遍:把 nums[1] = 4 放进哈希集合(橙色)。集合会自动去重,并支持 O(1) 判断某个数在不在。放完所有数后再开始数连续。
- 6把 200 放进哈希集合第一遍:把 nums[2] = 200 放进哈希集合(橙色)。集合会自动去重,并支持 O(1) 判断某个数在不在。放完所有数后再开始数连续。
- 7把 1 放进哈希集合第一遍:把 nums[3] = 1 放进哈希集合(橙色)。集合会自动去重,并支持 O(1) 判断某个数在不在。放完所有数后再开始数连续。
- 8把 3 放进哈希集合第一遍:把 nums[4] = 3 放进哈希集合(橙色)。集合会自动去重,并支持 O(1) 判断某个数在不在。放完所有数后再开始数连续。
- 9把 2 放进哈希集合第一遍:把 nums[5] = 2 放进哈希集合(橙色)。集合会自动去重,并支持 O(1) 判断某个数在不在。放完所有数后再开始数连续。
- 10把 5 放进哈希集合第一遍:把 nums[6] = 5 放进哈希集合(橙色)。集合会自动去重,并支持 O(1) 判断某个数在不在。放完所有数后再开始数连续。
- 11把 6 放进哈希集合第一遍:把 nums[7] = 6 放进哈希集合(橙色)。集合会自动去重,并支持 O(1) 判断某个数在不在。放完所有数后再开始数连续。
- 12100 是起点,当前长度 = 1考察 100:前一个数 99 不在集合里,所以 100 是一段连续序列的起点(绿色)。从它开始往上数:当前序列 = [100],当前长度 cur_len = 1。
- 13101 不在集合 → 这段结束,长度 1101 不在集合里,从 100 起的这段连续序列到此结束:100,长度 1。这段里的每个数都被标成已数过(绿/灰),后面不会再从它们重复往上数。
- 143 在集合里 → 4 不是起点,跳过考察 4:它的前一个数 3 在集合里,说明 4 不是某段连续序列的起点(3 才是更靠前的起点)。直接跳过,绝不从这里往上数——这正是避免重复、保证 O(n) 的关键。
- 15200 是起点,当前长度 = 1考察 200:前一个数 199 不在集合里,所以 200 是一段连续序列的起点(绿色)。从它开始往上数:当前序列 = [200],当前长度 cur_len = 1。
- 16201 不在集合 → 这段结束,长度 1201 不在集合里,从 200 起的这段连续序列到此结束:200,长度 1。这段里的每个数都被标成已数过(绿/灰),后面不会再从它们重复往上数。
- 171 是起点,当前长度 = 1考察 1:前一个数 0 不在集合里,所以 1 是一段连续序列的起点(绿色)。从它开始往上数:当前序列 = [1],当前长度 cur_len = 1。
- 182 在集合 → 序列延伸,长度 = 2往上看 2:它在集合里,所以这段连续序列延伸到 2(绿色一整段 1→2)。当前长度 cur_len = 2,更新最长 longest = 2。
- 193 在集合 → 序列延伸,长度 = 3往上看 3:它在集合里,所以这段连续序列延伸到 3(绿色一整段 1→2→3)。当前长度 cur_len = 3,更新最长 longest = 3。
- 204 在集合 → 序列延伸,长度 = 4往上看 4:它在集合里,所以这段连续序列延伸到 4(绿色一整段 1→2→3→4)。当前长度 cur_len = 4,更新最长 longest = 4。
- 215 在集合 → 序列延伸,长度 = 5往上看 5:它在集合里,所以这段连续序列延伸到 5(绿色一整段 1→2→3→4→5)。当前长度 cur_len = 5,更新最长 longest = 5。
- 226 在集合 → 序列延伸,长度 = 6往上看 6:它在集合里,所以这段连续序列延伸到 6(绿色一整段 1→2→3→4→5→6)。当前长度 cur_len = 6,更新最长 longest = 6。
- 237 不在集合 → 这段结束,长度 67 不在集合里,从 1 起的这段连续序列到此结束:1→2→3→4→5→6,长度 6。这段里的每个数都被标成已数过(绿/灰),后面不会再从它们重复往上数。
- 242 在集合里 → 3 不是起点,跳过考察 3:它的前一个数 2 在集合里,说明 3 不是某段连续序列的起点(2 才是更靠前的起点)。直接跳过,绝不从这里往上数——这正是避免重复、保证 O(n) 的关键。
- 251 在集合里 → 2 不是起点,跳过考察 2:它的前一个数 1 在集合里,说明 2 不是某段连续序列的起点(1 才是更靠前的起点)。直接跳过,绝不从这里往上数——这正是避免重复、保证 O(n) 的关键。
- 264 在集合里 → 5 不是起点,跳过考察 5:它的前一个数 4 在集合里,说明 5 不是某段连续序列的起点(4 才是更靠前的起点)。直接跳过,绝不从这里往上数——这正是避免重复、保证 O(n) 的关键。
- 275 在集合里 → 6 不是起点,跳过考察 6:它的前一个数 5 在集合里,说明 6 不是某段连续序列的起点(5 才是更靠前的起点)。直接跳过,绝不从这里往上数——这正是避免重复、保证 O(n) 的关键。
- 28所有起点都数完,longest = 6所有起点都处理完了。最长的一段连续序列是 1→2→3→4→5→6(绿色),长度 6。整个过程:每个数入一次集合、每段只从起点往上数一次,总共 O(n)。
- 29return 6返回 longest = 6。注意 100 和 200 各自只是长度 1 的孤立序列,真正最长的是 1 到 6 这一段。
- 32记住这题的骨架:哈希集合负责 O(1) 查询,「只从起点起步」这个限制让总数数次数等于元素个数,把朴素 O(n²) 压成 O(n)。
⚠️ 容易写错的地方
✗ 错:先排序再扫
✓ 对:哈希集合 + 只从起点数
排序是 O(n log n),题目要求 O(n)
✗ 错:每个数都往上数
✓ 对:只在 x-1 不在集合时才数
不限制起点会退化成 O(n²),超时
✗ 错:忘记用集合去重
✓ 对:set(nums) 去重
重复数会让长度计数和起点判断出错
完整代码(Python / C++ / Java)
Python
class Solution:
def longestConsecutive(self, nums):
num_set = set(nums)
longest = 0
for x in num_set:
if x - 1 not in num_set:
cur, length = x, 1
while cur + 1 in num_set:
cur += 1
length += 1
longest = max(longest, length)
return longestC++
class Solution {
public:
int longestConsecutive(vector<int>& nums) {
unordered_set<int> s(nums.begin(), nums.end());
int longest = 0;
for (int x : s) {
if (s.count(x - 1)) continue;
int cur = x, length = 1;
while (s.count(cur + 1)) {
cur++;
length++;
}
longest = max(longest, length);
}
return longest;
}
};Java
import java.util.HashSet;
import java.util.Set;
class Solution {
public int longestConsecutive(int[] nums) {
Set<Integer> set = new HashSet<>();
for (int x : nums) set.add(x);
int longest = 0;
for (int x : set) {
if (set.contains(x - 1)) continue;
int cur = x, length = 1;
while (set.contains(cur + 1)) {
cur++;
length++;
}
longest = Math.max(longest, length);
}
return longest;
}
}复杂度
时间复杂度
O(n)
每个数最多被「往上数」访问一次
空间复杂度
O(n)
哈希集合存全部数
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 最长连续序列 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
这道题为什么用「哈希集合」,换最直接的暴力解会差在哪?+
哈希集合抓住了本题的结构特征,把暴力解里重复的工作省掉;暴力解通常要多嵌套一层枚举,数据一大就超时。具体对比见上文「暴力解及其卡点」与「最优解逐步推演」两节。
时间复杂度为什么是 undefined?怎么推出来的?+
按上文复杂度小节的推导,时间复杂度为 undefined。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 最长连续序列 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。