力扣刷题【01.28】

128. 最长连续序列

给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

请你设计并实现时间复杂度为 O(n) 的算法解决此问题。

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

题目分析
看到本题找最长连续序列,每个人都会想到两种最朴素的解法之一:

  • 先排序,从前往后找最长连续上升序列即可。该思路简单有效,但是复杂度已经至少有 O ( n l o g n ) O(nlogn) O(nlogn)。实现起来也比较简单,在此不讨论该解法。
  • 遍历数组中的每个元素num,然后以num为起点,每次+1向后遍历num+1,num+2,num+3…,判断这些元素是否存在于数组中。假设找到的最大的连续存在的元素为num+x,那么这个连续序列的长度即为x+1。最后将每个num所开始序列长度取个最大值即可。

优化的点主要有两个:
(1)判断num+1,num+2,num+3…是否在数组中。上面的代码是用直接遍历的方式去查找的,时间复杂度为 O ( n ) O(n) O(n),我们可以改为哈希表查找,时间复杂度为 O ( 1 ) O(1) O(1)
(2)遍历数组中每个元素num。逐一遍历每个元素会产生很多冗余工作,实际上我们无需一次针对每个元素num去判断num+1,num+2,num+3…是否在数组中。如果num-1已经在数组中的话,那么num-1肯定会进行相应的+1遍历,然后遍历到num,而且从num-1开始的+1遍历必定比从num开始的+1遍历得到的序列长度更长。因此,我们便可将在一个连续序列中的元素进行删减,让其只在最小的元素才开始+1遍历
比如,现有元素[1,2,4,3,5],当2,3,4,5发现均有比自己小1的元素存在,那么它们就不会开始+1遍历,而1是连续序列中最小的元素,没有比自己小1的元素存在,所以会开始+1遍历。 通过上述方式便可将时间复杂度优化至O(n)。

class Solution {
    public int longestConsecutive(int[] nums) {
        // 建立一个存储所有数的哈希表,同时起到去重功能
        Set<Integer> set = new HashSet<>();
        for (int num : nums) {
            set.add(num);
        }

        int ans = 0;
        // 遍历去重后的所有数字
        for (int num : set) {
            int cur = num;
            // 只有当num-1不存在时,才开始向后遍历num+1,num+2,num+3......
            if (!set.contains(cur - 1)) {
                while (set.contains(cur + 1)) {
                    cur++;
                }
            }
            // [num, cur]之间是连续的,数字有cur - num + 1个
            ans = Math.max(ans, cur - num + 1);
        }
        return ans;
    }
}

注意:上述代码虽然有两层循环for+while,但是由于if (!set.contains(cur - 1))判断的存在,每个元素只会被遍历一次,因此时间复杂度也为O(n)。

上面是一种朴素思路的优化方法,下面再介绍三种算法,分别为:

  1. 使用哈希表记录当前num能到达的连续最右边界;
    其实方法1相比上面的更容易想到吧
class Solution {
    public int longestConsecutive(int[] nums) {
        // key表示num,value表示num最远到达的连续右边界
        Map<Integer, Integer> map = new HashMap<>();
        // 初始化每个num的右边界为自己
        for (int num : nums) {
            map.put(num, num);
        }

        int ans = 0;
        for (int num : nums) {
            //if (!map.containsKey(num - 1)) {
                int right = map.get(num);
                // 遍历得到最远的右边界
                while (map.containsKey(right + 1)) {
                    right = map.get(right + 1);
                }
                // 更新右边界
                map.put(num, right);
                // 更新答案
                ans = Math.max(ans, right - num + 1);
           // }
            
        }
        return ans;
    }
}
  1. 使用哈希表记录当前值所在的连续最大区间(动态规划)
    用哈希表存储每个端点值对应连续区间的长度
    若数已在哈希表中:跳过不做处理
    若是新数加入:

    • 取出其左右相邻数已有的连续区间长度 left 和 right
    • 计算当前数的区间长度为:cur_length = left + right+ 1
    • 根据 cur_length 更新最大长度 max_length 的值 更新区间两端点的长度值
class Solution(object):
    def longestConsecutive(self, nums):
        hash_dict = dict()
        
        max_length = 0
        for num in nums:
            if num not in hash_dict:
                left = hash_dict.get(num - 1, 0)
                right = hash_dict.get(num + 1, 0)
                
                cur_length = 1 + left + right
                if cur_length > max_length:
                    max_length = cur_length
                
                hash_dict[num] = cur_length
                hash_dict[num - left] = cur_length
                hash_dict[num + right] = cur_length
                
        return max_length

  1. 并查集
    并查集实际上也是来记录右边界的,所有在一个连续区间内的元素都会在一个连通分量中,且这些元素的根结点都为最远的右边界元素。
    具体思路是:
    遍历所有元素num,如果num+1存在,将num加入到num+1所在的连通分量中;
    重新遍历一遍所有元素num,通过find函数找到num所在分量的根结点,也就是最远右边界,从而求得连续区间的长度。

参考题解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值