# 128. 最长连续序列
# 题目描述
给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
示例 1:
输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
2
3
示例 2:
输入:nums = [0,3,7,2,5,8,4,6,0,1]
输出:9
2
示例 3:
输入:nums = [1,0,1,2]
输出:3
2
提示:
0 <= nums.length <= 10^5-10^9 <= nums[i] <= 10^9
# 思路
在做本题时,直观的想法就是想到了先排个序,这样连续的数字相对来说,好判断很多了。
但题目要求O(n),就不能排序了。
如果我们希望做到 O(n),就必须想到一个关键问题:怎样才能 O(1) 判断某个数字是否存在?
这时候,我们隐隐约约感受到,应该用哈希表。
只是如何哈希,还没有想清楚,但可以奔这个方向去思考了。
因为题目中,要求的是相差只有1的连续序列。
我们可以从1到nums里的最大数,遍历一遍,通过set标记该数是否在nums里,同时记录一下长度就行。
但又看题目描述,nums里数的范围,-10^9 <= nums[i] <= 10^9
这就也不能这么做了。
再换一个思路,对每个数字都一直往右查下一个数字是否在哈希表中。
但这个思路最坏情况下(比如全是连续数)复杂度会变成 O(n²)。
那么我们重复了哪些遍历操作呢?
只有"连续序列的起点" 才需要向右扫描,如果该数字不是连续序列的起点,就不要扫描了,会造成重复扫描。
那么如何判断一个数字是连续序列的起点?
其实我们只需要判断它的前一个数字(num - 1)是否在数组nums里存在就好了!
# 模拟过程
我们用[100,4,200,1,3,2] 来举例。(因为我们会把数组提前放到set里去重,元素在set里是什么顺序 不确定,按照举例这个顺序来)
1、先看 100,100是不是连续序列的起点呢,看 (100-1)也就是 99 是否在 集合里。99不在集合里,说明 100 就是 一个连续序列的起点。 判断 (100+1)就是 101 是否在集合里,101不在集合里,以100为起点的连续序列长度就是1。
2、看 4,4是不是连续序列的起点呢,看 (4-1)也就是3 是否在集合里。3 在集合里,说明 4 不是连续序列起点,就不用继续判断了,因为 以4为起点的连续序列一定不是最长的,4的前面还有3呢。
3、看200,200是不是连续序列的起点呢,看 (200-1)也就是 199 是否在 集合里。199不在集合里,说明 200 就是 一个连续序列的起点。 判断 (200+1)就是 201 是否在集合里,201不在集合里,以200为起点的连续序列长度就是1。
4、看1,1 是不是连续序列的起点呢,看 (1-1)也就是 0 是否在 集合里。0不在集合里,说明 1 就是 一个连续序列的起点。
判断 (1+1) 就是 2 是否在集合里,2在集合,此时以1为起点的连续序列长度为2。
判断 (1+2) 就是 3 是否在集合里,3在集合,此时以1为起点的连续序列长度为3。
判断 (1+3) 就是 4 是否在集合里,4在集合,此时以1为起点的连续序列长度为4。
判断 (1+4) 就是 5 是否在集合里,5不在集合,停止继续判断,最终以 1 为起点的连续序列长度为4。
5、 看 3,3是不是连续序列的起点呢,看 (3-1)也就是2 是否在集合里。2 在集合里,说明 3 不是连续序列起点,就不用继续判断了,因为 以3为起点的连续序列一定不是最长的,3的前面还有2呢。
同理,看2,一样的推理。
最后取最长的连续序列,也就是4。
以上的模拟过程最关键的优化点,就是判断 当前数字(CurNum) 是不是连续序列的起点。理解这一点很重要。
# 解题代码
C++代码如下:
class Solution {
public:
int longestConsecutive(vector<int>& nums) {
unordered_set<int> uset;
// 将数组放进set,这里还做了去重
for (int num : nums) uset.insert(num);
// 保存结果
int result = 0;
for (int num : uset) {
// 注意这里 是搜 num - 1 是否在集合里
if (uset.find(num - 1) == uset.end()) {
// 如果不在集合,那么 num 就是起始点
int curNum = num;
int curLength = 1;
// 开始判断 curNum 连续的下一位(curNum + 1)是否在set里
while (uset.find(curNum + 1) != uset.end()) {
curLength++; // 如果在,则统计的长度加加
curNum++; // 如果在,则继续判断下一位
}
// 更新最长的长度
result = max(result, curLength);
}
}
return result;
}
};
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
时间复杂度为:O(n)
大家可能想,这分明 for里面 嵌套 while,时间复杂度应该是 O(n^2) 。
但细看 代码实现,uset里每个元素,最多只会被遍历两次。
一次是判断是否可能是起点,一次是判断是否在连续的区间里。
举个例子: 如果 uset里是 1,2,3,4,5。
判断 num-1 是否在 uset里,即 0 不在uset里,那么 1 可以作为起点。
后面while循环 会以此判断 2,3,4,5 也在uset中,记录连续长度。
再回到for循环后遍历 num = 2 ,判断 num - 1 即1 是否在uset里,1 已经在uset里,说明 此时的num(2)不是起点,所以直接pass,不会再去遍历了。
所以 if (uset.find(num - 1) == uset.end()) 是 本题代码的精髓所在。核心在于只有"连续序列的起点" 才需要向右扫描
# 其他语言
# python3
class Solution:
def longestConsecutive(self, nums):
num_set = set(nums)
result = 0
for num in num_set:
# 判断是否是起点
if num - 1 not in num_set:
cur = num
length = 1
while cur + 1 in num_set:
cur += 1
length += 1
result = max(result, length)
return result
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Java
import java.util.HashSet;
import java.util.Set;
class Solution {
public int longestConsecutive(int[] nums) {
Set<Integer> set = new HashSet<>();
for (int num : nums) set.add(num);
int result = 0;
for (int num : set) {
// 判断是否为起点
if (!set.contains(num - 1)) {
int cur = num;
int length = 1;
while (set.contains(cur + 1)) {
cur++;
length++;
}
result = Math.max(result, length);
}
}
return result;
}
}
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
# Go
func longestConsecutive(nums []int) int {
set := make(map[int]bool)
for _, num := range nums {
set[num] = true
}
result := 0
for num := range set {
// 判断是否为起点
if !set[num-1] {
cur := num
length := 1
for set[cur+1] {
cur++
length++
}
if length > result {
result = length
}
}
}
return result
}
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
# JS
var longestConsecutive = function(nums) {
const set = new Set(nums);
let result = 0;
for (let num of set) {
// 判断是否为起点
if (!set.has(num - 1)) {
let cur = num;
let length = 1;
while (set.has(cur + 1)) {
cur++;
length++;
}
result = Math.max(result, length);
}
}
return result;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# **与代码随想录联系 **
在代码随想录中,我们已经做过很多与 哈希结构、去重、O(1) 查找 相关的题,例如「两个数组交集 (opens new window)」「快乐数 (opens new window)」等。 这些题都有一个共同特点:
我们想快速判断某个数字是否存在。
而本题「最长连续序列」是同一类问题: 要不断判断下一个数字(num + 1)是否存在。
所以第一步想到:
用 unordered_set 存所有数字,去重 + O(1) 查找。
但如果对每一个数字都向右找下一位,最坏可能会变成 O(n²)。
这时候,引出本题解题关键:只有"连续序列的起点"才有必要向右找。
评论
验证登录状态...