LC 128 最长连续序列:别上来就排序,O (n) 解法才是这题的灵魂

刷到这道题的时候,我第一反应:这不排个序就完事了?遍历一遍数连续数字的长度,记录最大值,多简单啊。结果定睛一看题目要求时间复杂度 O (n),瞬间人傻了 ------ 排序本身都 O (nlogn) 了,这不是明摆着不让用排序嘛。

今天就聊聊这道经典题,从排序的朴素解法,到真正满足要求的 O (n) 解法,我把我踩过的坑都给你们捋一遍。

先说说题目到底要干啥

一句话说清题意:给你一个未排序的整数数组,找出数字连续的最长序列的长度。序列元素不用在原数组里挨在一起,只要数字本身是连续的就行。比如输入 [100,4,200,1,3,2],最长连续序列是 1、2、3、4,输出 4。

我的第一版:排序大法(能过但不对)

说实话,我第一反应写的就是排序。思路特别直白:先把数组从小到大排好序,然后从头往后遍历。如果当前数比前一个大 1,就说明连续,长度 +1;不然就重置长度,全程记录最大的长度值。

这个写法跑示例全对,提交也能 AC,但严格来说不符合题目 O (n) 的要求。面试的时候你这么写,面试官大概率会追问一句 "有没有时间复杂度更低的写法?"属于 "保底可以,但冲不了高分" 的解法。

正解来了:哈希集合 + 起点判断

想了好一会儿我才反应过来:要 O (1) 判断一个数字存不存在,那肯定用哈希集合啊。但光有集合还不够 ------ 总不能每个数字都往后挨个查有没有连续数吧,那不又退化成 O (n²) 了。

这里最关键的一个思路点,我当初琢磨了半天才想通:我们只从「连续序列的起点」开始计数。

什么叫起点?如果一个数字 num,它的前一个数 num - 1 不在集合里,那它一定是某段连续序列的开头。反过来,如果 num - 1 存在,那 num 肯定是某段序列中间的一个数,不用管它,等遍历到起点的时候自然会数到它。

就拿示例来说:集合里有 1、2、3、4、100、200。1 的前面 0 不存在,所以 1 是起点;2 的前面 1 存在,所以 2 不是起点,直接跳过。

这样一来,每个数字最多只会被访问一次。只有起点会进入内层循环往后数,中间的数都直接跳过,总的操作次数加起来就是 n 次,时间复杂度自然就是 O (n)。

我一开始还纳闷,这不也是嵌套循环吗?怎么就 O (n) 了?后来自己手算了一遍才明白:不是每个元素都会进内层循环,进循环的只有每个序列的第一个数,整体摊下来还是线性的。

代码实现

直接上 JavaScript 版本,亲测能提交通过:

javascript 复制代码
var longestConsecutive = function(nums) {
    // 空数组直接返回0,别问,问就是在这里错过一次
    if (nums.length === 0) return 0

    // 用Set去重 + 实现O(1)查找
    const numSet = new Set(nums)
    let maxLen = 0

    for (const num of numSet) {
        // 只有前一个数不存在时,才是序列的起点
        if (!numSet.has(num - 1)) {
            let currentNum = num
            let currentLen = 1

            // 从起点开始,往后数连续的数字
            while (numSet.has(currentNum + 1)) {
                currentNum++
                currentLen++
            }

            // 更新最大长度
            maxLen = Math.max(maxLen, currentLen)
        }
    }

    return maxLen
};

说个小细节:这里遍历的是 Set 而不是原数组。这样天然就避开了重复元素的问题,比如 [1,0,1,2] 这种有重复的情况,不会因为两个 1 重复计算。

复杂度分析

时间复杂度 O (n):每个元素最多被访问一次,整体线性时间。空间复杂度 O (n):需要一个哈希集合存储所有元素。

聊聊我踩过的坑

  1. 上来就写排序,写完才想起题目要 O (n),白忙活了好一会儿。
  2. 最开始写哈希版的时候,每个数都进循环往后数,直接超时,完全没达到 O (n) 的效果。
  3. 忘了处理空数组的边界情况,提交直接报错,社死现场。

最后碎碎念

其实这题本身逻辑不难,难的是跳出 "排序" 的惯性思维。那个 "只从起点开始遍历" 的思路,我觉得是这题最精髓的地方 ------ 一下子就把嵌套循环的时间复杂度拉回了线性。

你第一次做这道题的时候,第一反应是排序吗?或者你有别的更有意思的解法?评论区聊聊,我每条都会看~

觉得有帮助的话,点个赞让更多小伙伴看到呀

相关推荐
To_OC4 小时前
LC 994 腐烂的橘子:人人都说是 BFS 入门题,我却写了三遍才过
javascript·算法·leetcode
金銀銅鐵8 小时前
[Python] 扩展欧几里得算法
python·数学·算法
To_OC10 小时前
LC 200 岛屿数量:经典 DFS 入门题,我第一次写居然连方向都搞错了
javascript·算法·leetcode
labixiong12 小时前
实现一个能跑的迷你版Promise(一)
前端·javascript·面试
weedsfly17 小时前
还在用 Axios?你可能需要重新理解 XHR 与 Fetch
前端·javascript·面试
CoderWeen17 小时前
从零实现一个 Vue3 流程图编辑器:节点拖拽、贝塞尔连线与框选
前端·javascript
kyriewen1 天前
我用 50 行代码重写了 React Router 核心,终于搞懂了前端路由原理
前端·javascript·react.js
Asize1 天前
HTML5 Canvas 基础:从按帧动画到 ECharts 数据可视化
前端·javascript·canvas
默_笙1 天前
🎄 后端给我一堆扁平数据,我 10 行代码把它变成了树
前端·javascript