力扣hot100之128:最长连续序列
力扣hot100之128:最长连续序列

LeetCode 128. 最长连续序列

题目描述

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

最长连续序列是:

[0,1,2,3,4,5,6,7,8]

所以答案是 9


解题思路

这道题最关键的要求是:时间复杂度必须是 O(n)

如果先排序,再去统计连续序列,虽然思路很自然,但排序本身就需要 O(n log n),不满足题目要求。

所以这题的核心就是:

如何在不排序的情况下,快速判断某个数字的前一个数或后一个数是否存在。

这正是 HashSet 最擅长的事情。

你给出的代码如下:

class Solution {
    public int longestConsecutive(int[] nums) {
        Set<Integer> st = new HashSet<>();
        for (int num : nums) {
            st.add(num); 
        }
        int m = st.size();
        int ans = 0;
        for (int x : st) { 
            if (st.contains(x - 1)) { 
                continue;
            }
            int y = x + 1;
            while (st.contains(y)) { 
                y++;
            }
            ans = Math.max(ans, y - x); 
            if (ans * 2 >= m) {
                break;
            }
        }
        return ans;
    }
}

这份代码的核心思想非常经典:把所有数字放进 HashSet,然后只从连续序列的起点开始向后枚举。


为什么要用 HashSet

因为 HashSet 可以在平均 O(1) 的时间内判断一个元素是否存在。

比如我们想知道:

  • x - 1 是否存在

  • x + 1 是否存在

  • x + 2 是否存在

如果用数组或者链表来找,每次都要遍历,效率会很低。 但如果用 HashSet,这些判断都可以很快完成。

所以这题第一步就是:

Set<Integer> st = new HashSet<>();
for (int num : nums) {
    st.add(num);
}

这样做还有一个额外好处:

自动去重。

因为题目中数组里可能有重复元素,而连续序列只关心某个数是否存在,不关心它出现了多少次。


为什么只从“起点”开始枚举

这是这道题最关键的优化。

假设集合里有这样一段连续序列:

1, 2, 3, 4, 5

如果我们从每个数都开始往后找:

  • 从 1 开始找一遍

  • 从 2 开始找一遍

  • 从 3 开始找一遍

  • 从 4 开始找一遍

  • 从 5 开始找一遍

那就会有大量重复计算。

所以我们要想办法只从真正的“起点”开始找。

什么叫起点

如果一个数 x 的前一个数 x - 1 不存在,那么 x 就是某个连续序列的起点。

比如:

  • 1 前面没有 0,所以 1 是起点

  • 2 前面有 1,所以 2 不是起点

  • 3 前面有 2,所以 3 不是起点

所以代码里有这句:

if (st.contains(x - 1)) {
    continue;
}

意思是:

  • 如果 x - 1 存在,说明 x 不是起点

  • 那就跳过,避免重复枚举

这样每一段连续序列只会被统计一次。


核心思路总结

这道题可以概括成三步:

  1. 把所有元素放进 HashSet

  2. 遍历集合中的每个数,只从连续序列的起点开始处理

  3. 从起点不断向后查找,统计连续长度,并更新答案


代码解析

1. 先把所有数字加入集合

Set<Integer> st = new HashSet<>();
for (int num : nums) {
    st.add(num); 
}

这里把数组元素放进 HashSet 中,方便后续快速查找。


2. 获取集合大小并初始化答案

int m = st.size();
int ans = 0;
  • m 表示去重后的元素个数

  • ans 表示当前找到的最长连续序列长度


3. 遍历集合中的每一个数

for (int x : st) {

这里遍历的是集合,不是原数组。 这样可以避免重复数字带来的干扰。


4. 如果不是起点,就跳过

if (st.contains(x - 1)) {
    continue;
}

如果 x - 1 存在,说明 x 不是某段连续序列的开头。

比如在序列 1,2,3,4 中:

  • 2 前面有 1

  • 3 前面有 2

  • 4 前面有 3

所以这些点都不需要重新开始统计。


5. 从起点开始向后扩展

int y = x + 1;
while (st.contains(y)) {
    y++;
}

x + 1 开始,如果集合中存在这个数,就说明连续序列还在延续,于是继续向后找。

直到某个数不存在为止。

比如从 1 开始:

2 存在
3 存在
4 存在
5 不存在

那么连续序列就是 1,2,3,4


6. 更新答案

ans = Math.max(ans, y - x);

这里为什么是 y - x

因为 y 已经停在了第一个不存在的位置。

例如:

  • x = 1

  • 最后 y = 5

说明连续序列是:

1,2,3,4

长度正好就是:

5 - 1 = 4

7. 剪枝优化

if (ans * 2 >= m) {
    break;
}

这是你代码里的一个小优化。

含义是: 如果当前找到的最长连续序列长度已经足够大,大到超过集合元素数的一半,那么继续找下去提升答案的空间已经很有限,可以直接结束。

这个剪枝不是必须的,不写也能通过。 它只是一个额外优化。


示例推演

我们以这个例子来模拟:

nums = [100,4,200,1,3,2]

先放入集合:

st = {100, 4, 200, 1, 3, 2}

遍历到 100

检查 99 是否存在:

99 不存在

说明 100 是起点。

往后找:

  • 101 不存在

所以这一段长度为:

101 - 100 = 1

更新答案:

ans = 1

遍历到 4

检查 3 是否存在:

3 存在

说明 4 不是起点,跳过。


遍历到 200

检查 199 是否存在:

199 不存在

说明 200 是起点。

往后找:

  • 201 不存在

长度为 1,答案仍然是 1


遍历到 1

检查 0 是否存在:

0 不存在

说明 1 是起点。

往后找:

  • 2 存在

  • 3 存在

  • 4 存在

  • 5 不存在

所以连续序列为:

1,2,3,4

长度为:

5 - 1 = 4

更新答案:

ans = 4

最终返回 4


为什么这题能做到 O(n)

很多同学第一次看到这里会疑惑:

外层有 for,内层又有 while,为什么总复杂度不是 O(n^2)

关键就在于:

每个数字最多只会被“向后扩展”访问一次。

因为我们只从起点开始枚举。

比如序列:

1,2,3,4,5

只有 1 会进入 while,而 2,3,4,5 都不会作为起点再次进入扩展过程。

所以整体来看,每个元素最多只会被处理常数次,总时间复杂度仍然是 O(n)


和排序解法对比

排序解法

思路:

  1. 先排序

  2. 再遍历统计最长连续长度

优点:

  • 容易想到

  • 逻辑比较直观

缺点:

  • 排序需要 O(n log n)

  • 不满足题目要求的线性时间复杂度


HashSet 解法

思路:

  1. 用集合快速查找元素是否存在

  2. 只从起点开始扩展

优点:

  • 时间复杂度 O(n)

  • 自动去重

  • 是这道题最经典的解法

缺点:

  • 需要一定的哈希思维

  • 第一次想到“只从起点开始找”不太容易


复杂度分析

设去重后集合中有 m 个元素。

时间复杂度

O(n)

  • 插入 HashSet 需要 O(n)

  • 遍历集合并扩展连续序列,整体仍然是 O(n)

所以总时间复杂度是线性的。

空间复杂度

O(n)

需要一个 HashSet 来存储所有元素。


总结

这道题最核心的两个点就是:

  1. HashSet 实现快速查找

  2. 只从连续序列的起点开始向后扩展

也就是说,真正的关键不是“找连续”,而是:

怎样避免重复地找连续。

只要把这一点想清楚,这道题就不难了。

你可以把这题总结成一句话:

先去重放进集合,再从每段连续序列的起点开始往后数长度。

这是哈希表题里非常经典的一道题,也是一道很能体现“用空间换时间”思想的题目。


如果对您有帮助的话,能否支持一下博主?
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇