128. 最长连续序列
问题描述
给定一个未排序的整数数组 nums
,找出数字连续的最长序列的长度(要求序列元素在原数组中可以不连续相邻)。要求算法的时间复杂度为 O(n)。
示例:
输入: [100, 4, 200, 1, 3, 2]
输出: 4
解释: 最长连续序列是 [1, 2, 3, 4],长度为 4
算法思路
哈希集合法(最优解)
-
核心思想
利用 HashSet 实现 O(1) 时间复杂度的元素查找,通过识别连续序列的起点
(即当前数字的前一个数不在集合中),然后向后扩展计算连续序列长度。 -
操作步骤
- 将所有数字存入 HashSet
- 遍历数组中的每个数字:
- 若
num-1
不在集合中 → 说明num
是连续序列起点 - 从
num
开始向后扩展(num+1, num+2,...
),统计连续序列长度
- 若
- 更新最长连续序列长度
代码实现
import java.util.HashSet;
import java.util.Set;
class Solution {
public int longestConsecutive(int[] nums) {
// 创建哈希集合并添加所有元素
Set<Integer> numSet = new HashSet<>();
for (int num : nums) {
numSet.add(num);
}
int maxLength = 0; // 记录最长连续序列长度
for (int num : numSet) {
// 只有当 num 是连续序列起点时才处理(即 num-1 不在集合中)
if (!numSet.contains(num - 1)) {
int currentNum = num; // 当前检查的数字
int currentLength = 1; // 当前连续序列长度
// 向后扩展:检查 num+1, num+2, ... 是否存在
while (numSet.contains(currentNum + 1)) {
currentNum++;
currentLength++;
}
// 更新最长连续序列长度
maxLength = Math.max(maxLength, currentLength);
}
}
return maxLength;
}
}
注释
-
创建哈希集合
Set<Integer> numSet = new HashSet<>(); for (int num : nums) { numSet.add(num); }
- 将所有元素存入 HashSet,实现 O(1) 时间复杂度的元素查找
-
识别序列起点
if (!numSet.contains(num - 1)) {
- 仅当
num
是连续序列起点时处理(前驱数字不存在)
- 仅当
-
向后扩展序列
while (numSet.contains(currentNum + 1)) { currentNum++; currentLength++; }
- 从起点开始向后扩展,统计连续序列长度
-
更新最大长度
maxLength = Math.max(maxLength, currentLength);
- 实时更新全局最长连续序列长度
算法分析
- 时间复杂度:O(n)
每个元素最多被访问两次(一次在遍历集合时,一次在向后扩展时),整体 O(n) - 空间复杂度:O(n)
哈希集合存储所有元素
算法过程
输入:nums = [100, 4, 200, 1, 3, 2]
- 构建集合:
{100, 4, 200, 1, 3, 2}
- 遍历元素:
100
:99
不存在 → 起点
向后扩展:101
不存在 → 长度=14
:3
存在 → 跳过200
:199
不存在 → 起点
向后扩展:201
不存在 → 长度=11
:0
不存在 → 起点
向后扩展:2,3,4
存在 → 长度=43
:2
存在 → 跳过2
:1
存在 → 跳过
- 输出:
maxLength=4
边界处理
- 空数组:直接返回 0
- 重复元素:HashSet 自动去重,不影响结果
- 整数边界:处理
Integer.MIN_VALUE
和Integer.MAX_VALUE
的边界情况
测试用例
public static void main(String[] args) {
Solution solution = new Solution();
// 测试用例1:标准示例
int[] nums1 = {100, 4, 200, 1, 3, 2};
System.out.println("Test 1: " + solution.longestConsecutive(nums1)); // 4
// 测试用例2:有重复元素
int[] nums2 = {0, 0, 1, 2, 3, 4, 5};
System.out.println("Test 2: " + solution.longestConsecutive(nums2)); // 6
// 测试用例3:空数组
int[] nums3 = {};
System.out.println("Test 3: " + solution.longestConsecutive(nums3)); // 0
// 测试用例4:单个元素
int[] nums4 = {5};
System.out.println("Test 4: " + solution.longestConsecutive(nums4)); // 1
// 测试用例5:多个连续序列
int[] nums5 = {10, 20, 30, 11, 12, 13};
System.out.println("Test 5: " + solution.longestConsecutive(nums5)); // 4
// 测试用例6:负数序列
int[] nums6 = {-5, -4, -3, 0, 1};
System.out.println("Test 6: " + solution.longestConsecutive(nums6)); // 3
}
关键点
-
起点识别:
通过检查num-1
是否在集合中,确保每个连续序列只被处理一次 -
去重处理:
使用 HashSet 自动处理重复元素,避免重复计数 -
时间复杂度保证:
- 每个元素最多访问两次(起点识别 + 向后扩展)
- 整体时间复杂度严格 O(n)
-
空间复杂度优化:
- 相比排序法(O(n log n) 时间复杂度),此解法满足 O(n) 时间复杂度要求
- 空间复杂度 O(n) 是必要开销
扩展
- 并查集解法:
可用并查集维护连续序列,但实现较复杂且空间开销更大 - 排序解法:
先排序后遍历,时间复杂度 O(n log n),不满足题目要求 - 流式处理:
适用于超大数据集(外排序+遍历),但时间复杂度仍为 O(n log n)