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