LeetCode 128. 最长连续序列,这道题的核心考点不是排序(毕竟排序时间复杂度至少O(nlogn)),而是如何用O(n)的时间,找到未排序数组中数字连续的最长序列长度——看似矛盾的要求,其实只要找对数据结构和解题思路,就能轻松破解。
今天就来详细拆解这道题,从题目理解、思路推导,到代码实现、复杂度分析,一步步讲清楚,无论是刚入门的新手,还是想巩固算法基础的同学,都能看懂、学会、会用。
一、题目回顾:明确要求,避开陷阱
先再仔细看一遍题目,避免理解偏差:
给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
这里有两个关键信息,必须重点注意:
-
序列要求:数字连续即可,不需要元素在原数组中是相邻的(比如数组 [100,4,200,1,3,2],最长连续序列是 [1,2,3,4],长度为4);
-
复杂度限制:必须是 O(n) 时间,这就直接排除了“先排序,再遍历找最长连续”的思路(排序的时间复杂度是 O(nlogn),不符合要求)。
很多同学一开始会陷入排序的思维定式,导致思路走偏,其实只要跳出这个误区,用“哈希集合”来优化查询效率,就能满足 O(n) 的时间要求。
二、核心思路:用哈希集合“去重+快速查询”,避免重复遍历
既然不能排序,那我们就要想办法快速判断一个数字的“前一个数字”“后一个数字”是否存在于数组中——这时候,哈希集合(Set)就是最佳选择,因为 Set 的 has() 方法查询时间复杂度是 O(1),正好契合我们的需求。
核心思路拆解(3步走):
-
去重处理:将数组元素存入 Set 中,不仅能快速查询,还能自动去除重复元素(重复元素不影响连续序列长度,比如 [1,1,2],最长连续序列还是 [1,2],长度2);
-
找到序列的“起点”:遍历 Set 中的每个数字,如果这个数字的前一个数字(num-1)不在 Set 中,说明这个数字是某一个连续序列的起点(比如数字1,若0不在Set中,1就是序列的起点);
-
延伸序列,计算长度:从起点开始,依次查询后一个数字(num+1、num+2...)是否在 Set 中,直到查询不到为止,统计这个序列的长度,并用一个变量记录所有序列中的最长长度。
这里有一个非常关键的优化点:只从序列起点开始延伸。这样就能避免重复遍历——比如序列 [1,2,3,4],我们只在遍历到1(起点)时,才会延伸计算整个序列的长度;遍历到2、3、4时,因为它们的前一个数字(1、2、3)都在 Set 中,不是起点,所以直接跳过,不会重复计算,这就保证了整体时间复杂度是 O(n)。
三、代码实现:逐行解析,一看就懂
结合上面的思路,我们可以写出简洁高效的代码(TypeScript 版本,JavaScript 版本只需去掉类型定义即可),逐行解析如下,新手也能轻松跟上:
function longestConsecutive(nums: number[]): number {
// 1. 将数组元素存入Set,去重+O(1)查询
const set = new Set(nums);
// 2. 记录最长连续序列长度,初始值为0(数组为空时返回0)
let maxLen = 0;
// 3. 遍历Set中的每个数字
for (const num of set) {
// 4. 判断当前数字是否是序列起点(num-1不在Set中)
if (!set.has(num - 1)) {
let len = 1; // 当前序列长度,起点本身占1位
let next = num + 1; // 下一个要查询的数字
// 5. 延伸序列,查询next是否在Set中,直到查询不到为止
while (set.has(next)) {
len++; // 序列长度+1
next++; // 继续查询下一个数字
}
// 6. 更新最长序列长度
maxLen = Math.max(maxLen, len);
}
}
// 7. 返回最长连续序列长度
return maxLen;
};
关键代码逐行解读(重点必看):
-
const set = new Set(nums):这一步是基础,时间复杂度 O(n)(遍历数组存入Set),空间复杂度 O(n)(存储数组所有元素,去重后最多还是n个);
-
if (!set.has(num - 1)):核心判断,筛选出序列起点,避免重复遍历,这是保证 O(n) 时间的关键;
-
while (set.has(next)):从起点延伸,每次查询 next 是否存在,存在则序列长度+1,直到查询不到,这一步整体遍历下来是 O(n)(每个元素只被遍历一次);
-
maxLen = Math.max(maxLen, len):每次计算出一个序列的长度后,和当前最长长度对比,更新最长长度。
四、复杂度分析:为什么是 O(n) 时间、O(n) 空间?
这道题的复杂度是很多同学的疑问,这里详细说明,确保大家理解到位:
时间复杂度:O(n)
整体分为3个部分,所有操作的时间总和是 O(n):
-
将数组存入 Set:遍历数组一次,O(n);
-
遍历 Set 中的元素:Set 中最多有 n 个元素(无重复),所以遍历一次是 O(n);
-
while 循环延伸序列:每个元素只会被延伸一次(比如 [1,2,3,4],只有1会触发while循环,2、3、4不会),所以整个while循环的总执行次数是 O(n)。
三者相加,整体时间复杂度是 O(n) + O(n) + O(n) = O(n),完全满足题目要求。
空间复杂度:O(n)
主要是 Set 占用的空间,Set 中存储了数组的所有元素(去重后),最坏情况下(数组无重复),空间复杂度是 O(n);最好情况下(数组全是重复元素),空间复杂度是 O(1),但整体最坏情况是 O(n),所以空间复杂度按 O(n) 计算。
五、总结:解题关键与易错点
这道题看似有难度,但核心就是“用 Set 优化查询,找序列起点避免重复遍历”,掌握这两个关键点,就能轻松写出 O(n) 时间复杂度的代码。
解题关键
-
用 Set 实现 O(1) 快速查询,解决“判断数字前后是否存在”的核心问题;
-
只从序列起点延伸,避免重复遍历,保证时间复杂度为 O(n)。
易错点提醒
-
忘记去重:如果不使用 Set 去重,重复元素会导致多次判断,虽然不影响最终结果,但会增加不必要的遍历,影响效率;
-
误将“数组元素连续”当作“序列连续”:题目不要求序列元素在原数组中连续,只需数字连续即可,无需排序;
-
忽略空数组和单个元素的边界情况:这两种情况是高频测试点,需确保代码能正确处理(初始 maxLen 设为0,即可覆盖)。
其实,这道题的思路本质上是“哈希表的应用优化”,只要理解了“避免重复遍历”的核心,就能举一反三,解决类似的“连续序列”“快速查询”类题目。