# 「力扣」第 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

# 理解题意

  • 序列:即子序列,不需要连续;
  • 严格上升,并且间隔是 ,才能形成最长。

关键的地方在于连续。

# 方法一:暴力解法(时间复杂度不符合要求)

  • 先排序,然后逐个判断是否连续。

参考代码

import java.util.Arrays;

public class Solution {

    public int longestConsecutive(int[] nums) {
        int len = nums.length;
        if (len < 2) {
            return len;
        }

        Arrays.sort(nums);

        int longestLen = 1;
        int res = 1;
        int pre = nums[0];
        for (int i = 1; i < len; i++) {
            if (nums[i] == nums[i - 1]) {
                // 重复元素要去掉
                continue;
            } else if (nums[i] == (pre + 1)) {
                longestLen++;
                res = Math.max(res, longestLen);
            } else {
                longestLen = 1;
            }
            pre = nums[i];
        }
        return res;
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        // int[] nums = new int[]{100, 4, 200, 1, 3, 2};
        int[] nums = new int[]{1, 2, 0, 1};
        int res = solution.longestConsecutive(nums);
        System.out.println(res);
    }
}

复杂度分析

  • 时间复杂度:
  • 空间复杂度:

# 方法二:哈希表

  • 空间换时间:每个数会被看 次。

参考代码

import java.util.HashSet;
import java.util.Set;

public class Solution {

    public int longestConsecutive(int[] nums) {
        int len = nums.length;
        if (len < 2) {
            return len;
        }

        // 除了快速查找,还有去重的效果,如果有很多起点,会重复计算
        Set<Integer> hashSet = new HashSet<>(len);
        for (int num : nums) {
            hashSet.add(num);
        }

        // 最长连续子序列的长度
        int res = 0;
        for (int num : hashSet) {
            // 关键:保证连续序列的起点最小
            if (hashSet.contains(num - 1)) {
                continue;
            }

            int longestLen = 1;
            // 遍历找出以 num 为起点的间隔为 1 的最长连续子序列
            while (hashSet.contains(num + 1)) {
                longestLen++;
                num++;
            }

            res = Math.max(res, longestLen);
        }
        return res;
    }
}

复杂度分析

  • 时间复杂度:,每一个数会被看 遍;
  • 空间复杂度:

# 方法三:针对方法二的优化

参考代码

import java.util.HashMap;
import java.util.Map;

public class Solution {

    public int longestConsecutive(int[] nums) {
        int len = nums.length;
        if (len < 2) {
            return len;
        }

        // key:nums[i] 中的数值
        // value:以 nums[i] 为边界的「连续」数组的长度,只有边界才有意义
        Map<Integer, Integer> hashMap = new HashMap<>(len);

        int res = 1;
        for (int num : nums) {
            if (hashMap.containsKey(num)) {
                continue;
            }

            Integer leftBound = hashMap.get(num - 1);
            Integer rightBound = hashMap.get(num + 1);

            if (leftBound == null && rightBound == null) {
                hashMap.put(num, 1);
            } else if (leftBound != null && rightBound != null) {
                int longestLen = leftBound + rightBound + 1;
                res = Math.max(res, longestLen);

                // num 只需要占一个位置即可,num - leftBound 和 num + rightBound 的定义需要准确
                hashMap.put(num, 0);
                hashMap.put(num - leftBound, longestLen);
                hashMap.put(num + rightBound, longestLen);
            } else if (leftBound == null) {
                int longestLen = rightBound + 1;
                res = Math.max(res, longestLen);

                hashMap.put(num, longestLen);
                hashMap.put(num + rightBound, longestLen);
            } else {
                // rightBound == null
                int longestLen = leftBound + 1;
                res = Math.max(res, longestLen);

                hashMap.put(num, longestLen);
                hashMap.put(num - leftBound, longestLen);
            }
        }
        return res;
    }

    // 100(1), 4(4),200(1),1(4),3(2),2(0)

    public static void main(String[] args) {
        Solution3 solution3 = new Solution3();
        int[] nums = new int[]{100, 4, 200, 1, 3, 2};

        // int[] nums = new int[]{4, 0, -4, -2, 2, 5, 2, 0, -8, -8, -8, -8, -1, 7, 4, 5, 5, -4, 6, 6, -3};
        // int[] nums = new int[]{1, 2, 0, 1};
        int res = solution3.longestConsecutive(nums);
        System.out.println(res);
    }
}

复杂度分析

  • 时间复杂度:,每一个数会被看 遍;
  • 空间复杂度:

# 方法四:并查集

  • 并查集底层用「哈希表」实现;
  • 因此设计 size ,这里是「按秩合并」的思想,「秩」表示以并查集根结点为子树的结点的个数;
  • 改造 union 方法,返回合并以后的新的连通分量的结点个数。

注意:这里封装的「并查集」有一些特殊,union 方法返回的是 size ,可以理解成基于 size 合并的意思,这里的 size 在这道问题里是有实际意义。

参考代码

import java.util.HashMap;
import java.util.Map;

public class Solution {

    public int longestConsecutive(int[] nums) {
        int len = nums.length;
        if (len < 2) {
            return len;
        }

        UnionFind unionFind = new UnionFind(nums);
        int res = 1;
        for (int num : nums) {
            if (unionFind.contains(num - 1)) {
                res = Math.max(res, unionFind.union(num, num - 1));
            }

            if (unionFind.contains(num + 1)) {
                res = Math.max(res, unionFind.union(num, num + 1));
            }
        }
        return res;
    }

    /**
     * 由于数值是离散的,parent 数组使用哈希表代替
     */
    private class UnionFind {

        private Map<Integer, Integer> parent;
        // 维护以当前结点为根的子树的结点总数
        private Map<Integer, Integer> size;

        public UnionFind(int[] nums) {
            int len = nums.length;
            parent = new HashMap<>(len);
            size = new HashMap<>(len);

            for (int num : nums) {
                parent.put(num, num);
                size.put(num, 1);
            }
        }

        /**
         * union 方法返回了以合并以后的连通分量的结点个数
         *
         * @param x
         * @param y
         * @return
         */
        public int union(int x, int y) {
            int rootX = find(x);
            int rootY = find(y);

            if (rootX == rootY) {
                return 0;
            }

            int sizeX = size.get(rootX);
            int sizeY = size.get(rootY);

            int sum = sizeX + sizeY;
            if (sizeX < sizeY) {
                parent.put(rootX, rootY);
                size.put(rootY, sum);
            } else {
                parent.put(rootY, rootX);
                size.put(rootX, sum);
            }
            return sum;
        }

        public int find(int x) {
            while (x != parent.get(x)) {
                // 实现了路径压缩,底下那些结点错了没有关系,根结点对就可以了
                parent.put(x, parent.get(parent.get(x)));
                x = parent.get(x);
            }
            return x;
        }

        /**
         * 新增 contains 方法
         *
         * @param x
         * @return
         */
        public boolean contains(int x) {
            return parent.containsKey(x);
        }
    }
}

复杂度分析

  • 时间复杂度:平均意义下 ,,每一个数会被看 遍;
  • 空间复杂度:

作者:liweiwei1419 链接:https://suanfa8.com/union-find/solutions/0128-longest-consecutive-sequence 来源:算法吧 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Last Updated: 11/19/2024, 1:33:17 AM