「这是我参与2022首次更文挑战的第34天,活动详情查看:2022首次更文挑战」
题目介绍
给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
示例1
输入: nums = [100,4,200,1,3,2]
输出: 4
解释: 最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
示例2
输入: nums = [0,3,7,2,5,8,4,6,0,1]
输出: 9
提示:
0 <= nums.length <= 10^5-10^9 <= nums[i] <= 10^9
解题思路
这道题利用排序、去重,然后计算最长的连续序列是比较容易想到的做法,但是在这里我们不做考虑,因为排序的时间复杂度已经超过了题目要求的 O(n)。这里讲一下另外三种方法。
思路一:哈希表
遍历数组中的所有元素,然后在哈希表中记录包含当前元素在内的最长连续序列的长度,计算方式是 比当前元素小 1 的连续序列长度 + 比当前元素大 1 的连续序列长度 + 1,然后由于连续序列的两个端点还有可能被后续的元素使用到,因此连续序列的两个端点处的长度也需要变更
解题步骤
- 如果当前数组为空,连续序列长度将为 0
- 创建哈希表
map,遍历nums数组中的元素 - 如果元素在哈希表中存在,说明之前出现过,当前的这个元素不需要重复操作
- 从哈希表中获取
当前元素 - 1和当前元素 + 1的连续序列的长度,并计算当前元素连续序列的长度比当前元素小 1 的连续序列长度 + 比当前元素大 1 的连续序列长度 + 1 - 在哈希表中记录当前元素的连续序列的长度,更新连续序列两边端点的长度
- 获取哈希表中记录的所有元素连续序列长度的最大值
解题代码
var longestConsecutive = function(nums) {
if (!nums.length) return 0
const map = new Map()
for (const num of nums) {
if (map.has(num)) continue
const left = map.get(num - 1) || 0
const right = map.get(num + 1) || 0
const len = left + right + 1
map.set(num, len)
map.set(num - left, len)
map.set(num + right, len)
}
return Math.max(...map.values())
};
思路二:集合
这种做法是将每个元素都放到集合中,这样在查找元素的时间复杂度是 O(1),我们可以在遍历每个元素时,查找当前元素 +1、 +2、 +3…… 是否在集合中,如果是就计算连续序列的最长长度,然后与当前记录的最长长度比较取较大值
但是我们不需要对每个元素都这样操作,如果 当前元素 - 1 的元素在集合中,那么我们就不需要操作当前元素,因为从 当前元素 - 1 开始计算出的连续序列长度肯定比当前元素的大
解题步骤
- 将所有元素放到集合
set中 - 遍历
nums中的元素 - 如果
num - 1在集合中不存在,则查找num + 1、num + 2、num + 3…… 是否存在,并记录最长序列长度 - 返回最大序列长度
解题代码
var longestConsecutive = function(nums) {
const set = new Set(nums)
let max = 0
for (const num of set) {
if (!set.has(num - 1)) {
currentLen = 1
currentNum = num
while (set.has(currentNum + 1)) {
currentLen++
currentNum++
}
max = Math.max(max, currentLen)
}
}
return max
};
思路三:并查集
并查集是遍历 nums 中的元素 num,如果 num - 1 存在,就将 num 和 num - 1 的下标连通起来,如果 num + 1 存在,就将 num 和 num + 1 的下标连通起来,最后返回并查集中最大的集合的大小即可
解题步骤
- 定义并查集,大小为
nums长度,定义哈希表map用于记录每个点的下标 - 遍历数组中的元素,如果元素在哈希表中出现过,则跳过
- 在哈希表中记录当前元素的下标
- 如果哈希表中存在
num + 1,则连通两个元素的下标 - 如果哈希表中存在
num - 1,则连通两个元素的下标 - 返回并查集中最大集合的大小
解题代码
var longestConsecutive = function(nums) {
if (!nums.length) return 0
const unionSet = new UnionSet(nums.length)
const map = new Map()
for (let i = 0; i < nums.length; i++) {
if (map.has(nums[i])) continue
map.set(nums[i], i)
map.has(nums[i] - 1) && unionSet.merge(i, map.get(nums[i] - 1))
map.has(nums[i] + 1) && unionSet.merge(i, map.get(nums[i] + 1))
}
return Math.max(...unionSet.size)
};
class UnionSet {
constructor(n) {
this.fa = []
this.size = []
for (let i = 0; i < n; i++) {
this.fa[i] = i
this.size[i] = 1
}
}
get(v) {
if(this.fa[v] === v) return v
const root = this.get(this.fa[v])
this.fa[v] = root
return root
}
merge(a, b) {
const ra = this.get(a), rb = this.get(b)
if (ra === rb) return
if (this.size[ra] < this.size[rb]) {
this.fa[ra] = rb
this.size[rb] += this.size[ra]
} else {
this.fa[rb] = ra
this.size[ra] += this.size[rb]
}
}
}