[路飞]_最长连续序列

170 阅读4分钟

「这是我参与2022首次更文挑战的第34天,活动详情查看:2022首次更文挑战

leetcode-128 最长连续序列

题目介绍

给定一个未排序的整数数组 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,然后由于连续序列的两个端点还有可能被后续的元素使用到,因此连续序列的两个端点处的长度也需要变更

演示文稿 7.gif

解题步骤

  1. 如果当前数组为空,连续序列长度将为 0
  2. 创建哈希表 map,遍历 nums 数组中的元素
  3. 如果元素在哈希表中存在,说明之前出现过,当前的这个元素不需要重复操作
  4. 从哈希表中获取 当前元素 - 1当前元素 + 1 的连续序列的长度,并计算当前元素连续序列的长度 比当前元素小 1 的连续序列长度 + 比当前元素大 1 的连续序列长度 + 1
  5. 在哈希表中记录当前元素的连续序列的长度,更新连续序列两边端点的长度
  6. 获取哈希表中记录的所有元素连续序列长度的最大值

解题代码

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 开始计算出的连续序列长度肯定比当前元素的大

演示文稿 7(1).gif

解题步骤

  1. 将所有元素放到集合 set
  2. 遍历 nums 中的元素
  3. 如果 num - 1 在集合中不存在,则查找 num + 1num + 2num + 3…… 是否存在,并记录最长序列长度
  4. 返回最大序列长度

解题代码

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 存在,就将 numnum - 1 的下标连通起来,如果 num + 1 存在,就将 numnum + 1 的下标连通起来,最后返回并查集中最大的集合的大小即可

演示文稿 7(2).gif

解题步骤

  1. 定义并查集,大小为 nums 长度,定义哈希表 map 用于记录每个点的下标
  2. 遍历数组中的元素,如果元素在哈希表中出现过,则跳过
  3. 在哈希表中记录当前元素的下标
  4. 如果哈希表中存在 num + 1,则连通两个元素的下标
  5. 如果哈希表中存在 num - 1,则连通两个元素的下标
  6. 返回并查集中最大集合的大小

解题代码

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