# 128. 最长连续序列

题目链接 (opens new window)

# 题目描述

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

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

示例 1:

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

示例 2:

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

示例 3:

输入:nums = [1,0,1,2]
输出:3
1
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;
    }
};
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

时间复杂度为: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
1
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;
    }
}
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

# 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
}
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

# 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;
};
1
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²)。

这时候,引出本题解题关键:只有"连续序列的起点"才有必要向右找。


上次更新:: 4/22/2026, 9:10:06 AM

评论

验证登录状态...