LeetCode 128. 最长连续序列(哈希表/并查集)

183 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

题目

方法一:哈希表

暴力匹配的思路是枚举数组中的每一个数xx,以该数为起点,不断尝试匹配x+1x+1x+2x+2\dots是否存在,对于匹配的过程,可以用O(n)O(n)的复杂度去暴力遍历数组,更高效的方法可以用哈希表提前存储好数组中的数,这样可以用O(1)O(1)复杂度来查找一个数是否存在,即使这样整个算法的复杂度最坏情况下仍然为O(n2)O(n^2),外层需枚举O(n)O(n)个数,内层也需暴力匹配O(n)O(n)次。

上述过程其实做了很多重复工作,假如一直有一个xxx+1x+1x+2x+2\dots的连续序列,那么就没有必要再以x+1x+1或者x+2x+2等等开始枚举了,得到的结果肯定不会优于以xx为起点的答案,因此外层每次枚举时利用哈希表判断是否存在x1x-1,若存在则跳过此数。

增加了判断跳过的逻辑后,时间复杂度为O(n)O(n)外层循环需要O(n)O(n)的时间复杂度,但是数组中的每个数只会进入内层循环一次。

class Solution {
    public int longestConsecutive(int[] nums) {
        int n = nums.length;
        Set<Integer> set = new HashSet<>();
        for (int num : nums) {
            set.add(num);
        }
        int res = 0;
        for (int i = 0; i < n; i++) {
            if (!set.contains(nums[i] - 1)) {
                int curNum = nums[i];
                int cur = 1;
                while (set.contains(curNum + 1)) {
                    curNum += 1;
                    cur++;
                }
                res = Math.max(res, cur);
            }
        }
        return res;
    }
}
  • 时间复杂度:O(n)O(n)nn为数组的长度
  • 空间复杂度:O(n)O(n)

方法二:并查集

所有在一个连续区间的元素都会在一个连通分量中,且该连通分量的根节点为该连续区间中的最大值

  1. 遍历数组,如果nums[i]+1nums[i]+1存在,将nums[i]nums[i]加入nums[i]+1nums[i]+1所在的连通分量中
  2. 重新遍历数组,通过find函数找到nums[i]nums[i]所在分量的根节点,也就是其所在连续区间的最大值,从而求出连续区间的长度
class UnionFind {
    /**
     * 记录每个节点的父节点
     */
    private Map<Integer, Integer> parent;
​
    public UnionFind(int[] nums) {
        parent = new HashMap<>();
        for (int num : nums) {
            // 初始化父节点为自身
            parent.put(num, num);
        }
    }
​
    /**
     * 寻找x的父节点,实际上也就是x的最远连续右边界,也就是其所在连续区间的最大值
     * @param x
     * @return
     */
    public Integer find(int x) {
        if (!parent.containsKey(x)) {
            return null;
        }
​
        while (x != parent.get(x)) {
            // 路径压缩
            parent.put(x, parent.get(parent.get(x)));
            x = parent.get(x);
        }
        return x;
    }
​
    /**
     * 合并两个连通分量,在本题中只用来将num并入到num+1的连续区间中
     * @param x
     * @param y
     */
    public void union(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX == rootY) {
            return ;
        }
        // 将num所在树的根节点设置为num+1所在树的根节点
        parent.put(rootX, rootY);
    }
}
class Solution {
    public int longestConsecutive(int[] nums) {
        UnionFind uf = new UnionFind(nums);
        int ans = 0;
        
        for (int num : nums) {
            // 当num+1存在,将num合并到num+1所在集合中
            if (uf.find(num + 1) != null) {
                uf.union(num, num + 1);
            }
        }
​
        for (int num : nums) {
            // 找到num的最远连续右边界,也就是其所在连续区间的最大值
            int right = uf.find(num);
            ans = Math.max(ans, right - num + 1);
        }
        return ans;
    }
}
  • 时间复杂度:O(n)O(n),路径压缩后的并查集时间复杂度近似为O(1)O(1)
  • 空间复杂度:O(n)O(n)

Reference