最长连续序列:哈希集合的巧妙运用
在力扣上面刷到的这道题目,提交了一直超时,才发现遍历了原数组,没有去重,这道题目的思路还是很重要的,所以,写一篇文章分享分享
问题分析
给定一个未排序的整数数组 nums,需要找出其中数字连续的最长序列的长度,并且要求时间复杂度为 O(n)。
举个例子:
输入: [100, 4, 200, 1, 3, 2]
输出: 4
解释: 最长连续序列是 [1, 2, 3, 4]
我的解题思路
第一步:建立哈希集合去重
首先我想到要用哈希集合来存储数组的所有元素,这样做有两个好处:
- 快速查询某个数是否存在(O(1)时间复杂度)
- 自动去重,避免重复数字干扰
let cnt = new Set(nums); // 去重 + 快速查询
第二步:关键优化——只从序列起点开始查找
这是我算法的核心优化点。如果直接遍历每个数都向后查找,会有大量重复计算。
我的观察:一个连续序列只需要从它的最小数开始查找就行了。
比如序列 [1, 2, 3, 4],从1开始查找可以得到整个序列,但如果从2、3、4开始查找,只会得到部分结果,是重复计算。
如何判断一个数是不是序列起点呢?
很简单:如果 num-1 不存在于集合中,那么 num 就是某个连续序列的起点。
// 只有当前数是序列起点时才进行处理
if(!cnt.has(num-1)) {
// 从起点开始向后扩展
}
第三步:从起点向后扩展计算长度
确定了起点后,就从这个数开始,不断检查 num+1、num+2... 是否存在,直到遇到不存在的数字为止。
let sum = num; // 从当前数开始
while(cnt.has(sum)) {
sum++; // 向后查找连续的数
}
第四步:计算并更新最大长度
当while循环结束时,sum 指向的是连续序列最后一个数的下一个值。
所以序列长度就是 sum - num。
maxlen = Math.max(maxlen, sum - num);
完整代码实现
/**
* @param {number[]} nums
* @return {number}
*/
var longestConsecutive = function(nums) {
// 1. 将数组转为哈希集合,自动去重
let cnt = new Set(nums);
let maxlen = 0;
let sum = 0;
// 2. 遍历集合(遍历集合而不是原数组,避免处理重复数)
for(const num of cnt) {
sum = num;
// 关键优化:只有当前数是连续序列的起点时才处理,这样我们就避免了重复的计算
// 判断标准:num-1 不在集合中
if(!cnt.has(num-1)) {
// 从起点开始向后查找连续的数
while(cnt.has(sum)) {
sum++;
}
}
// 3. 更新最大长度
// 此时 sum 是最后一个连续数的下一个值,所以长度 = sum - num
maxlen = Math.max(maxlen, sum - num);
}
return maxlen;
};
算法执行示例
让我用 nums = [100, 4, 200, 1, 3, 2, 2] 来演示:
-
建立集合:
{100, 4, 200, 1, 3, 2}(去掉了重复的2) -
遍历集合:
-
遇到100:99不存在 → 是起点
向后找:101不存在 → 序列[100],长度 = 1 -
遇到4:3存在 → 不是起点 → 跳过
-
遇到200:199不存在 → 是起点
向后找:201不存在 → 序列[200],长度 = 1 -
遇到1:0不存在 → 是起点
向后找:2存在,3存在,4存在,5不存在
序列[1, 2, 3, 4],长度 = 4 -
遇到3:2存在 → 不是起点 → 跳过
-
遇到2:1存在 → 不是起点 → 跳过
-
-
最终结果:4
时间复杂度分析
- 每个元素最多被访问两次(一次在外层循环,一次在内层while)
- 所有操作都是哈希集合的 O(1) 操作
- 总时间复杂度:O(n)
为什么这个方法高效?
- 避免排序:排序需要 O(n log n),我们直接用 O(n)
- 避免重复计算:通过"只从起点开始"的优化,确保每个连续序列只被完整遍历一次
- 空间换时间:使用 O(n) 的额外空间(哈希集合)来获得 O(n) 的时间复杂度
总结
这道题的核心在于理解如何高效地找到连续序列。我的思路是:
- 用哈希集合去重 + 快速查询
- 只从序列的起点开始向后扩展
- 确保每个序列只被计算一次
这种"空间换时间"的思想在算法面试中非常常见,记住这个模式,可以解决很多类似的问题。