📝 题目描述

题目链接最长连续序列

给定一个未排序的整数数组nums,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

请你设计并实现时间复杂度为O(n)O(n)的算法解决此问题。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
示例 1:

输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。

示例 2:

输入:nums = [0,3,7,2,5,8,4,6,0,1]
输出:9

示例 3:

输入:nums = [1,0,1,2]
输出:3

提示:

  • 0 <= nums.length <= 10^5
  • -10^9 <= nums[i] <= 10^9

💡 解题思路

方法一:哈希表

首先是暴力解法,对于数组中的每一个元素n,以n作为起点,不断匹配n+1是否存在、n+2是否存在,直到n+x是否存在,假设最多匹配到n+x,那么此序列长度就是x+1

优化1:对于匹配的过程,我们可以提前将所有元素放入哈希表,这样查看一个元素是否存在的复杂度就从O(n)O(n)降低至O(1)O(1)

但假设整个数组就是一个连续的序列,算法的复杂度还是会到达O(n2)O(n^2),仔细分析这个过程,会发现其中执行了很多不必要的枚举,如果已知有一个 n,n+1,n+2,⋯,n+x 的连续序列,而我们却重新从 n+1n+2 或者是 n+x 处开始重新尝试匹配。

优化2:对于一个元素n在开始匹配之前,我们可以先找一下n-1是否存在,如果存在,那么从n开始匹配得到的序列长度肯定不是最长的。并且只要n-1存在,我们迟早会/已经遍历以n-1为开头的序列,这样剪枝即可进一步降低复杂度。

总结一下,外层循环需要O(n)O(n)的时间复杂度,只有当一个数是连续序列的第一个数的情况下才会进入内层循环,然后在内层循环中匹配连续序列中的数,因此数组中的每个数只会进入内层循环一次。总时间复杂度为O(n)O(n)

🔧 代码实现

1、哈希表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Solution {
public:
int longestConsecutive(vector<int>& nums) {
// 创建无序集合
unordered_set<int> hash_set;
// 初始化答案
int res = 0;
// 向集合插入数据
for(int& i : nums){
hash_set.insert(i);
}
// 遍历集合,查找最长连续序列
for(const int& j : hash_set){
int tmp = 1;
int current_num = j;
// 查找j-1是否存在
if(hash_set.find(current_num - 1) != hash_set.end()){
// 存在,证明j-1不是连续序列的开头,跳过
continue;
} else {
// 不存在,证明j-1是连续序列的开头
// 寻找j+1是否存在,计算序列长度
while(hash_set.count(current_num + 1)){
tmp++;
current_num = current_num + 1;
}
// 更新答案
res = max(res, tmp);
}
}
return res;
}
};

📊 复杂度分析

  • 时间复杂度:外层循环需要O(n)O(n)的时间复杂度,内层只会循环一次,总时间复杂度为O(n)O(n)
  • 空间复杂度:哈希表存储数组中所有的数需要O(n)O(n)的空间。

🎯 总结

  • 核心思想:如何判断一个数字是否为序列的第一个数。