最长连续序列(leetcode 128)

2023-12-05 13:43:37 浏览数 (1)

1.问题描述

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

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

示例 1:

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

示例 2:

代码语言:javascript复制
输入:nums = [0,3,7,2,5,8,4,6,0,1]
输出:9

2.难度等级

medium。

3.热门指数

★★★★☆

出题公司:阿里、腾讯、百度、字节。

4.解题思路

方法一:排序

根据本题的描述,一般来说,最容易想到的就是先将 nums 进行排序,然后再从排序后的数组头部开始遍历,如果存在nums[i] 1,则进行加1计数。只要不存在 nums[i] 1,则从 0 开始重新执行计数操作。那么,每当发生了“断点”,如果当前连续序列长度大于 result 则更新 result 值,result 表示最长连续序列的长度。

但是由于本题目要求实现时间复杂度为 O(n) 的算法解决此问题,那么排序的做法我们就无法实现了。那么,我们还有什么别的方法来解决这道题吗?

方法二:哈希表

我们考虑枚举数组中的每个数 x,考虑以其为起点,不断判断 x 1,x 2,⋯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 一定是在数组中不存在前驱数 x−1。不然按照上面的分析我们会从 x−1 开始尝试匹配,因此我们每次在哈希表中检查是否存在 x−1 即能判断是否需要跳过了。

增加了判断跳过的逻辑之后,时间复杂度是多少呢?外层循环需要 O(n) 的时间复杂度,只有当一个数是连续序列的第一个数的情况下才会进入内层循环,然后在内层循环中匹配连续序列中的数,因此数组中的每个数只会进入内层循环一次。根据上述分析可知,总时间复杂度为 O(n),符合题目要求。

5.实现示例

下面以 Golang 为例,给出上面的实现。

注意,Golang 中如果哈希表只有 key 没有 value,建议使用 map[any]struct{},因为空结构体不占用内存空间。

代码语言:javascript复制
func longestConsecutive(nums []int) int {
    m := make(map[int]struct{}, len(nums))
    for _, n := range nums {
        m[n] = struct{}{}
    }
    var longestStreak int
    for x := range m {
        // 跳过逻辑
        if _, ok := m[x-1]; ok {
            continue
        }
        // 计算连续子序列长度
        y := x   1
        for ;;y  {
            if _, ok := m[y]; !ok {
                break
            }
        }
        if y - x > longestStreak {
            longestStreak = y-x
        }
    }
    return longestStreak
}

LeetCode 运行结果如下:

参考文献

128. 最长连续序列 - leetcode

0 人点赞