题目
给定一个未排序的整数数组,找出最长连续序列的长度。
要求算法的时间复杂度为 O(n)。
示例:
输入: [100, 4, 200, 1, 3, 2]
输出: 4
解释: 最长连续序列是 [1, 2, 3, 4]。它的长度为 4。
解题思路
一种简单直接的思路是枚举数组中的每个数 x,考虑以其为起点,不断尝试匹配 x+1, x+2,⋯ 是否存在,假设最长匹配到了 x+y,那么以 x 为起点的最长连续序列即为 x, x+1, x+2,⋯,x+y,其长度为 y+1,不断枚举并更新答案即可。
这种普通的暴力法去遍历数组查看是否存在这个数时间复杂度是 O(n),但其实更高效的方法是用一个哈希表存储数组中的数,这样就能以 O(1) 的时间复杂度查看一个数是否存在。这是典型的以空间换时间的做法。
但是这样算法的时间复杂度最坏情况下还是会达到 O(n^2)(即外层需要枚举O(n) 个数,内层需要暴力匹配 O(n)次),无法满足题目的要求。但仔细分析这个过程,我们会发现其中执行了很多不必要的枚举,如果已知有一个 x,x+1,x+2,⋯,x+y 的连续序列,那么以 x+1,x+2 或者是 x+y 为起点的连续序列其实就不需要匹配了,因此在外层循环的时候遇到这种情况跳过即可。
如果要枚举的数 x 在数组中存在前驱数 x−1,按照上面的分析 x 就不需要匹配了,因此我们每次在哈希表中检查是否存在前驱数 x-1 就能判断是否需要跳过了。
增加了判断跳过的逻辑之后,外层循环需要 O(n) 的时间复杂度,只有当一个数是连续递增序列的最小值的情况下才会进入内层循环,然后在内层循环中继续匹配连续序列中的其他数字。
假设最终有 m 组连续递增序列,每次都会在找到各组连续递增序列的最小值时,才会去求这个连续递增序列的长度,最差情况下,这组连续递增序列的最小值在这个序列的所有数之后,就需要遍历两次该组连续递增序列的所有数字才可以确定这个序列的长度。假设每个连续递增序列长度为 ni (1=<i<=m,且 n1+…+nm = 数组总长度),最差情况下都会遍历 2*ni 次才确定这个递增序列的长度,所以确定完所有递增序列的长度是遍历了 (2*n1+2*n2+…+2*nm = 2*数组总长度),故最差复杂度为 O(2N),去掉常数为 O(N)。
复杂度分析:
时间复杂度:O(n),其中 n 为数组的长度。
空间复杂度:O(n)。哈希表存储数组中所有的数需要 O(n) 的空间。
代码
class Solution {
public int longestConsecutive(int[] nums) {
// 用HashSet存储数字,并去重
Set<Integer> set = new HashSet<>();
for(int num : nums){
set.add(num);
}
// 记录最终最长连续序列的长度
int len = 0;
for(int num : set){
// 如果不存在前驱数字,就从当前数字开始向后查找与当前数字连续的数
if(!set.contains(num-1)){
int cur_num = num;
int cur_len = 1; // 记录当前最长连续序列的长度
while(set.contains(cur_num+1)){
cur_num += 1;
cur_len += 1;
}
len = Math.max(len, cur_len);
}
}
return len;
}
}