LeetCode 128. 最长连续序列 - 哈希集合解法详解
一、问题理解
「最长连续序列」问题要求:给定一个未排序的整数数组 nums,找出其中最长的连续元素序列的长度。这里的"连续"指的是数值连续,例如 [1, 2, 3, 4] 是长度为 4 的连续序列,[100, 200] 是长度为 1 的两个独立序列。
关键限制:算法的时间复杂度需要尽可能低,最好能达到 O(n)。
示例:
text
输入: nums = [100, 4, 200, 1, 3, 2] 输出: 4 解释: 最长连续序列是 [1, 2, 3, 4],长度为 4
二、核心思路
暴力解法的局限性
最简单的解法是先对数组排序,然后遍历统计最长连续序列,时间复杂度为 O(n log n),这不能满足题目对 O(n) 时间复杂度的要求。
哈希集合优化思路
通过哈希集合(HashSet)的快速查找特性,可以将时间复杂度优化到 O(n):
-
去重 + 快速查找:用 HashSet 存储所有元素,既去重(重复元素不影响连续序列长度),又支持 O(1) 时间判断元素是否存在。
-
只从序列起点计算长度:对于每个元素
x,只有当x-1不存在于集合中时,x才是某个连续序列的起点,避免对同一序列的中间元素重复计算。 -
向后延伸计算长度:从起点
x开始,依次检查x+1, x+2, ...是否存在,直到找不到为止,这段连续序列的长度为y - x(y是第一个不存在的元素)。
三、代码逐行解析
Java 解法
java
class Solution {
public int longestConsecutive(int[] nums) {
// 1. 创建HashSet存储所有元素(去重+O(1)查找)
Set<Integer> st = new HashSet<>();
for (int num : nums) {
st.add(num);
}
// 2. 记录最长连续序列的长度(初始为0,空数组时直接返回0)
int ans = 0;
// 3. 遍历集合中的每个元素
for (int x : st) {
// 3.1 判断x是否是连续序列的起点:若x-1存在,说明x不是起点,跳过
if (st.contains(x - 1)) {
continue;
}
// 3.2 若x是起点,向后延伸找最长连续序列
int y = x + 1; // 从x的下一个元素开始检查
while (st.contains(y)) { // 只要y存在,就继续向后找
y++;
}
// 3.3 计算当前连续序列的长度(y - x),更新最长长度
ans = Math.max(ans, y - x);
}
// 4. 返回最长连续序列的长度
return ans;
}
}
Python 解法
python
from typing import List
class Solution:
def longestConsecutive(self, nums: List[int]) -> int:
# 1. 创建集合存储所有元素(去重+O(1)查找)
num_set = set(nums) # Python中可以直接将列表转为集合
# 2. 记录最长连续序列的长度
max_length = 0
# 3. 遍历集合中的每个元素
for num in num_set:
# 3.1 判断num是否是连续序列的起点:若num-1存在,说明num不是起点,跳过
if num - 1 in num_set:
continue
# 3.2 若num是起点,向后延伸找最长连续序列
current_num = num
current_length = 1
# 只要current_num+1在集合中,就继续向后找
while current_num + 1 in num_set:
current_num += 1
current_length += 1
# 3.3 更新最长长度
max_length = max(max_length, current_length)
# 4. 返回最长连续序列的长度
return max_length
四、Java 与 Python 语法对比
1. 集合创建与使用
| 操作 | Java | Python |
|---|---|---|
| 创建集合 | Set<Integer> set = new HashSet<>(); | num_set = set() 或 num_set = {1, 2, 3} |
| 添加元素 | set.add(num) | num_set.add(num) |
| 检查元素是否存在 | set.contains(num) | num in num_set |
| 遍历集合 | for (int x : set) { ... } | for num in num_set: |
| 集合大小 | set.size() | len(num_set) |
2. 循环与条件判断
| 操作 | Java | Python |
|---|---|---|
| for-each 循环 | for (int num : nums) { ... } | for num in nums: |
| while 循环 | while (condition) { ... } | while condition: |
| 跳过当前迭代 | continue; | continue |
| 最大值函数 | Math.max(a, b) | max(a, b) |
3. 类型声明与转换
| 操作 | Java | Python |
|---|---|---|
| 类型声明 | int[] nums | nums: List[int] |
| 列表转集合 | 需要循环添加 | set(nums) |
| 返回类型 | public int longestConsecutive(...) | def longestConsecutive(...) -> int: |
五、实例演示
以测试用例 nums = [100, 4, 200, 1, 3, 2] 为例,演示执行过程:
Java 执行过程:
-
初始状态:创建集合
st = {100, 4, 200, 1, 3, 2},ans = 0 -
遍历元素:
-
x = 100:检查x-1 = 99是否存在?否(是起点)。y = 101,集合中无 101,长度101-100 = 1→ans = 1 -
x = 4:检查x-1 = 3是否存在?是(3 在集合中)→ 跳过 -
x = 200:检查x-1 = 199是否存在?否(是起点)。y = 201,无 → 长度1→ans仍为 1 -
x = 1:检查x-1 = 0是否存在?否(是起点)。y = 2(存在)→y = 3(存在)→y = 4(存在)→y = 5(不存在)。长度5-1 = 4→ans = 4 -
x = 3:检查x-1 = 2是否存在?是 → 跳过 -
x = 2:检查x-1 = 1是否存在?是 → 跳过
-
-
返回结果:
ans = 4
Python 执行过程:
-
初始状态:创建集合
num_set = {100, 4, 200, 1, 3, 2},max_length = 0 -
遍历元素:
-
num = 100:检查99 in num_set?否(是起点)。current_num = 100,current_length = 1;检查101 in num_set?否 →max_length = 1 -
num = 4:检查3 in num_set?是 → 跳过 -
num = 200:检查199 in num_set?否(是起点)。current_num = 200,current_length = 1;检查201 in num_set?否 →max_length = 1 -
num = 1:检查0 in num_set?否(是起点)。current_num = 1,current_length = 1;检查2 in num_set?是 →current_num = 2,current_length = 2;检查3 in num_set?是 →current_num = 3,current_length = 3;检查4 in num_set?是 →current_num = 4,current_length = 4;检查5 in num_set?否 →max_length = 4 -
num = 3:检查2 in num_set?是 → 跳过 -
num = 2:检查1 in num_set?是 → 跳过
-
-
返回结果:
max_length = 4
六、复杂度分析
时间复杂度:O(n)
-
每个元素最多被访问两次:
-
一次是在遍历集合时作为
x被检查 -
一次是在 while 循环中作为延伸的元素被访问
-
-
例如序列
[1, 2, 3, 4]中:-
1 被检查一次(作为起点)
-
2、3、4 在 while 循环中访问一次
-
-
整体遍历次数为 O(n)
空间复杂度:O(n)
-
哈希集合需要存储所有元素
-
最坏情况下(无重复元素)空间为 O(n)
七、优化技巧与变体
1. 使用更简洁的 Python 写法
python
class Solution:
def longestConsecutive(self, nums: List[int]) -> int:
num_set = set(nums)
max_length = 0
for num in num_set:
# 只有当num是序列起点时才计算
if num - 1 not in num_set:
current_num = num
current_length = 1
while current_num + 1 in num_set:
current_num += 1
current_length += 1
max_length = max(max_length, current_length)
return max_length
2. 使用字典记录边界长度(优化延伸操作)
python
class Solution:
def longestConsecutive(self, nums: List[int]) -> int:
# 使用字典记录每个数字所在的连续序列长度
num_dict = {}
max_length = 0
for num in nums:
if num in num_dict:
continue
# 获取左右相邻数字的序列长度
left_length = num_dict.get(num - 1, 0)
right_length = num_dict.get(num + 1, 0)
# 当前数字所在的序列长度
current_length = left_length + right_length + 1
# 更新当前数字和序列边界的长度
num_dict[num] = current_length
num_dict[num - left_length] = current_length
num_dict[num + right_length] = current_length
max_length = max(max_length, current_length)
return max_length
复杂度分析:
-
时间复杂度:O(n),每个元素只处理一次
-
空间复杂度:O(n),字典存储所有元素
八、常用函数积累
Java 常用函数
java
// 集合操作 Set<Integer> set = new HashSet<>(); set.add(num); // 添加元素 set.contains(num); // 检查元素是否存在 set.size(); // 获取集合大小 set.isEmpty(); // 检查集合是否为空 set.remove(num); // 删除元素 // 数组转集合 Set<Integer> set = new HashSet<>(Arrays.asList(1, 2, 3)); // 数学函数 Math.max(a, b); // 返回最大值 Math.min(a, b); // 返回最小值 Math.abs(num); // 返回绝对值
Python 常用函数
python
# 集合操作
num_set = set()
num_set.add(num) # 添加元素
num in num_set # 检查元素是否存在
len(num_set) # 获取集合大小
num_set.remove(num) # 删除元素(元素不存在会报错)
num_set.discard(num) # 删除元素(元素不存在不会报错)
# 列表转集合
num_set = set([1, 2, 3]) # 方法1
num_set = {1, 2, 3} # 方法2(直接创建)
# 内置函数
max(a, b) # 返回最大值
min(a, b) # 返回最小值
abs(num) # 返回绝对值
# 字典操作
num_dict = {}
num_dict.get(key, default) # 获取值,不存在时返回默认值
num_dict[key] = value # 设置值
key in num_dict # 检查键是否存在
九、总结
核心要点
-
哈希集合是关键:利用 HashSet 的 O(1) 查找特性,将查找时间从 O(n) 优化到 O(1)
-
避免重复计算:只从每个连续序列的起点开始计算,避免对同一序列的中间元素重复计算
-
时间复杂度优化:通过"只处理起点"的策略,确保每个元素最多被访问两次,实现 O(n) 时间复杂度
面试常见问题
-
为什么使用 HashSet 而不是先排序?
-
排序的时间复杂度是 O(n log n),而 HashSet 解法可以达到 O(n)
-
-
为什么只从起点开始计算?
-
避免重复计算同一序列。如果从中间元素开始计算,会多次计算同一序列的不同部分
-
-
如何处理空数组?
-
算法天然处理空数组:集合为空时,循环不会执行,直接返回初始值 0
-
-
如果有重复元素怎么办?
-
HashSet 会自动去重,重复元素不影响结果
-
-
这个解法在什么情况下会退化为 O(n²)?
-
正常情况下不会。每个元素最多被访问两次,所以是 O(n)
-
如果所有元素都是连续的(如 [1, 2, 3, ..., n]),while 循环会遍历整个序列,但每个元素仍然只被访问一次
-
扩展思考
这个问题的解法展示了"空间换时间"的经典思想,通过使用额外的哈希集合存储数据,将查找时间从线性优化到常数。类似的思想在解决很多算法问题时都非常有用,例如:
-
两数之和问题(使用哈希表)
-
字母异位词分组(使用哈希表存储排序后的字符串)
-
寻找重复元素(使用哈希集合)
掌握这种思想,能够帮助你在面试中快速识别并解决需要优化查找时间的问题。

被折叠的 条评论
为什么被折叠?



