LeetCode 👉 HOT 100 👉 最长连续序列 - 中等题

143 阅读3分钟

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

题目

给定一个未排序的整数数组 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

思路

要寻找最长的连续序列,很明显,如果数组 nums 是有序的话,那么只要遍历一次,按照寻找 x, x + 1, x + 2 ... 的规律去统计每个连续序列的长度,每次都取最大的即可;

无序数组转变为有序,使用 Array.sort 的话,显然不满足题目要求时间复杂度为 O(n) 的要求,但是思路是比较常规的。

排序后比较解法


    var longestConsecutive = function(nums) {

        if(nums.length == 0) return 0;

        // 排序
        nums.sort((a, b) => a - b);

        // 最长连续序列长度
        let max = 1;

        // 当前某个序列的连续长度
        let curMax = 1;

        for(let i = 0; i < nums.length; i++) {
            let cur = nums[i], next = nums[i + 1];

            // 重复的直接跳过
            if(cur === next) continue;

            // 满足连续序列
            if(cur + 1 === next) {
                curMax++;
            } else {
                // 不满足,重置curMax
                curMax = 1;
            }

            max = Math.max(max, curMax);
        }

        return max;
    }

思考: 如果不对数组进行排序,该怎么做?例如,当前 nums[i] == 2 时,该序列的下一个值应该为 3,那么 nums 中有没有 3 呢?要遍历去寻找么?显然不能遍历寻找,否则时间复杂度又不满足题目要求了。但可以先把 nums 转变为 Set 结构,在查找时,使用 Set.has 就可以了;于是如下思路便很容易想到

使用Set

    var longestConsecutive = function(nums) {
        if(nums.length == 0) return 0;

        // 先转换为Set结构
        let stack = new Set(nums), max = 1;

        for(let i = 0; i < nums.length; i++) {
            let curMax = 1, curNum = nums[i];

            while(stack.has(curNum + 1)) {
                curMax++;
                curNum++;
            }

            max = Math.max(max, curMax);
        }

        return max;
    }

继续思考: 上面的解法满足题目要求么?好像并不满足。循环的内部使用到的 while,将时间复杂度拉长了;对于如下用例:

[1, 2, 3, 4, 5, 6]

在遍历到 2 时,这次的遍历是有必要的么?显然没必要,因为在遍历到 1 时,它的最长序列会包括 2 形成的最长序列;同理对于 3, 4, 5, 6 都是无效的遍历;它们有一个共同的特性,就是都不为连续序列的 起点;判断当前遍历的,是不是 序列起点,可以使用 Set.has(num[i] - 1) 是否存在判断;因为作为 起点,它的左边不能有存在连续数字。

优化Set结构后的解法

    var longestConsecutive = function(nums) {
        if(nums.length == 0) return 0;

        // 先转换为Set结构
        let stack = new Set(nums), max = 1;

        for(let i = 0; i < nums.length; i++) {

            // 优化:不是起点,可以跳过
            if(!stack.has(nums[i] - 1)) {
                let curMax = 1, curNum = nums[i];
                while(stack.has(curNum + 1)) {
                    curMax++;
                    curNum++;
                }

                max = Math.max(max, curMax);
            }
        }

        return max;
    }

上述解法的时间复杂度是符合题目要求的,因为对于 nums[i] 来说,被枚举的次数最多为两次,一次在判断是不是 起点 时,一次在枚举 nums[i] + 1;即时间复杂度为 O(2n)2 为常数,不随 n 变化,即为 O(n)

小结

使用适合的数据结构,往往能为解法带来很大的便利;如本题的 Set 结构,在查找某个数时,将时间复杂度降低为 O(1),同时还对数组去重了;另外,像 Array ,可以模仿栈的结构,获得先进后出的特性;

时刻铭记: 程序 = 数据结构 + 算法

LeetCode 👉 HOT 100 👉 最长连续序列 - 中等题

合集:LeetCode 👉 HOT 100,有空就会更新,大家多多支持,点个赞👍

如果大家有好的解法,或者发现本文理解不对的地方,欢迎留言评论 😄