[路飞]最长连续序列

954 阅读3分钟

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

记录 1 道算法题

最长连续序列 - 并查集

leetcode-cn.com/problems/lo…


题目的要求是给定一个乱序的数组,然后返回里面最长的连续的数的个数。

比如 [100,4,200,1,3,2],最长的连续的数是 1,2,3,4。所以返回 4。

有可能传入空数组,所以在开头做长度判断。

里面会有一些重复的数,所以会简单的用一个对象进行去重。

    if(set[item]) continue
    set[item] = true
    
    ...

怎么在只遍历一遍的情况下得出答案呢。

这种集合的问题大多都能用并查集进行解决。主要的思路是收集每一个数字,并为他创建一个集合。当遇到与他相邻的数字就收集到自己的集合里。这时候会分为两种情况,1、与只有一个数字的集合进行合并。2、与拥有多个数字的集合进行合并,比如 [1,2] 和 [3,4] 进行合并。所以我们需要记录每一个集合的等级,由等级高吞并等级低。

为了让数字之间产生交集,以作为每次循环内进行合并的判断点,我们需要让相邻的数字产生联系。做法是每次遍历的时候除了生成当前数字的集合,也要生成当前数字的前一个数字的集合,即 prev。

有了这个 prev 之后,我们遍历的时候就可以判断当前的数字是否存在集合,是就进行继承,不是就创建集合。

然后对于 prev 的策略是,如果 prev 存在说明已经有之前循环的当前数字集合存在,那么就要与这轮循环的当前数字集合进行合并。如果不存在则创建 prev 的集合。

由于数组的不连续的,所以我们需要建立起数字和并查集集合列表下标的映射。

并查集集合列表下标与遍历时的 i 是一致的。

实现

    function longestConsecutive(nums) {
        const len = nums.length
        if (len === 0) return 0
        // 去重
        const set = {}
        // 映射
        const map = {}
        // 并查集集合列表
        const parent = Array.from(new Array(len), (_, i) => i)
        // 等级数组
        const size = new Array(len).fill(1)
        
        for(let i = 0; i < len; i++) {
            const item = nums[i]
            const prev = item - 1
            // 去重
            if (set[item]) continue
            set[item] = true
            
            if (map[item] !== undefined) {
                // 继承集合,通过其他数字留下的 prev 联系到该数字
                // 所以 +1
                const a = parent[i] = find(parent, map[item])
                size[a]++
            } else {
                // 创建集合,指向自己
                map[item] = i
            }
            
            if (map[prev] !== undefined) {
                // 比较集合等级,大合并小,然后规模扩大
                const a = parent[i]
                const b = find(parent, map[prev])
                if (size[b] > size[a]) {
                    const t = a
                    a = b
                    b = t
                }
                
                parent[b] = a
                size[a] += size[b]
            } else {
                // 创建集合,指向自己
                map[prev] = i
            }
        }
        
        return Math.max(...size)
    }
    
    function find(parent, i) {
        if (parent[i] !== i) {
            parent[i] = find(parent, parent[i])
        }
        return parent[i]
    }

哈希表解法

思路是先遍历一遍数组收集起来,顺便去重,然后再遍历一遍,对每个数进行再进行循环 +1,并计数,直到没有连续数字就停止,进行下一个数字。

优化的细节是当我们得到一个数字的时候,如果他有前一个数字,那说明他不是这个连续序列的起点,所以跳过。

    function longestConsecutive(nums) {
        if (nums.length === 0) return 0
        const map = {}
        // key value 都一样的,进行去重和收集。用 Set 也可以。
        for (let i = 0; i < nums.length; i++) {
          const a = nums[i]
          map[a] = a
        }

        let count = 1
        // 遍历去重过的对象
        const keys = Object.keys(map)
        for (let i = 0; i < keys.length; i++) {
          let a = map[keys[i]]
          // 当没有前一个的时候,
          // 说明是连续序列的开头,
          // 进行递增,计数
          if (map[a - 1] == undefined) {
            let _count = 0
            while (map[a] !== undefined) {
              _count++
              a++
            }
            
            // 比较出历史最大
            count = Math.max(count, _count)
          }
        }

        return count
    }

结束