力扣LeetCode 128题:最长连续序列

题目:

给定一个未排序的整数数组,找出最长连续序列的长度。
要求算法的时间复杂度为 O(n)。

示例:
输入: [100, 4, 200, 1, 3, 2]
输出: 4
解释: 最长连续序列是 [1, 2, 3, 4]。它的长度为 4。

分析

要找 连续序列 的长度,从结果可以看出,主要是为了排序,排序之后,遍历结果就可以进行连续长度的更新。

但是排序的算法,即使快排的时间复杂度也有O(nlogn),不满足要求。

这种时候一般就会想到借助哈希表,也就是类似计数排序,先来试试,用数组来做哈希表,下标做nums[i]。

1. 找出最大最小值(考虑有负值),这样就确定了哈希表的size
2. 遍历数组,对应的值在哈希表里++;
3. 遍历哈希表,非 0 元素连续长度的最大值,进行更新。
class Solution {
    public int longestConsecutive(int[] nums) {
        int max=0,min=0;
        for(int num: nums){
            max=Math.max(max,num);
            min=Math.min(min,num);
        }
        min=-min;
        int[] bucket=new int[max+min+1];

        for(int num: nums){
            bucket[num+min]++;
        }

        int ans=0,temp=0;
        int i=0;
        while(i<max+min+1){
            while(i<max+min+1 && bucket[i]!=0){
                temp++;
                i++;
            }
            ans=Math.max(ans,temp);
            temp=0;
            i++;
        }
        return ans;
    }
}

提交之后给我当头一棒:

最后执行的输入:
[2147483646,-2147483647,0,2,2147483644,-2147483645,2147483645]

可以看到,涉及到了整型的最小值和最大值问题,如果直接计算要使用的 bucket 的长度,一定会溢出,并且如果输入里有 int 的最小值,取反之后也要处理溢出。

上一种做法是,全部存下来,再去判断,而数组按照元素大小是连续存储的,这样肯定会有很多空间的浪费。

但是哈希的想法本身应该是没有问题的,我们来想想这个过程如何优化。

换成使用 HashSet 来存储本来的元素,这样第二次进行判断的时候我们只要查找当前的 nums[i] ,以及挨着他的元素 nums[i]++ 在不在 HashSet 中,如果在的话就给结果ans++,并且一路继续查找,否则就重新开始。
判断在不在的过程交给 contains() 方法来完成。

class Solution {
    public int longestConsecutive(int[] nums) {
        Set<Integer> set=new HashSet<>();

        for(int num: nums){
            set.add(num);
        }

        int ans=0;
        int count=0;
        for(int i=0;i<nums.length;i++){
            int num=nums[i];
            while(set.contains(num)){
                count++;
                num++;
            }
            ans=Math.max(ans,count);
            count=0;
        }
        return ans;
    }
}

这种方法显然是可以提交通过的(虽然时间复杂度达不到要求),但是重复的工作显然还是集中在了查找部分。

对于任何一个元素,都在 Set 里,去查找和他连续的数字的存在性,我们先不考虑查找过程的时间复杂度。

假设数组是 [1,2,3,4,5],对于1,已经查找过一次,计算出连续长度为 5 ,之后对于 2,3,4,5 又进行了重复的操作,这样最坏情况下时间复杂度总共就达到了内外的O(n2)

事实上,对于每一个连续序列而言,我们只希望判断一次,也就是从这个子序列的最小位置开始。那么**对于每一个元素 num ,先判断 num-1 是否存在,**就可以避免这种重复了。

修改后的代码如下:

        for(int i=0;i<nums.length;i++){
            int num=nums[i];
            if(!set.contains(num-1)){
                while(set.contains(num)){
                    count++;
                    num++;
                }
                ans=Math.max(ans,count);
                count=0;
            }
        }

也就是只多加一行 if(!set.contains(num-1)){

这时候的时间复杂度又是多少呢?

  1. 首先用 O(n) 时间把数组存入set;
  2. 遍历数组,每一个元素,只有不存在 num-1 ,才会进入计数阶段;这些处于各个连续序列开始位置的元素,都会进入一次,然后进行计数,总共正好组成了原数组的长度。而其他的元素在判断 num-1 存在之后不会进入计数的循环。因此时间复杂度还是 O(n) 。

所以总时间复杂度是O(n)。原因得益于HashMap本身的查找方法时间复杂度是O(1),个人认为也不是严格意义的O(1),可以看看这篇博客的源码分析:
HashMap底层源码分析

猜你喜欢

转载自blog.csdn.net/weixin_42092787/article/details/106598088