【Hot100|3 LeetCode 128. 最长连续序列】

LeetCode 128. 最长连续序列 - 哈希集合解法详解

一、问题理解

「最长连续序列」问题要求:给定一个未排序的整数数组 nums,找出其中最长的连续元素序列的长度。这里的"连续"指的是数值连续,例如 [1, 2, 3, 4] 是长度为 4 的连续序列,[100, 200] 是长度为 1 的两个独立序列。

关键限制:算法的时间复杂度需要尽可能低,最好能达到 O(n)。

示例

text

输入: nums = [100, 4, 200, 1, 3, 2]
输出: 4
解释: 最长连续序列是 [1, 2, 3, 4],长度为 4

二、核心思路

暴力解法的局限性

最简单的解法是先对数组排序,然后遍历统计最长连续序列,时间复杂度为 O(n log n),这不能满足题目对 O(n) 时间复杂度的要求。

哈希集合优化思路

通过哈希集合(HashSet)的快速查找特性,可以将时间复杂度优化到 O(n):

  1. 去重 + 快速查找:用 HashSet 存储所有元素,既去重(重复元素不影响连续序列长度),又支持 O(1) 时间判断元素是否存在。

  2. 只从序列起点计算长度:对于每个元素 x,只有当 x-1 不存在于集合中时,x 才是某个连续序列的起点,避免对同一序列的中间元素重复计算。

  3. 向后延伸计算长度:从起点 x 开始,依次检查 x+1, x+2, ... 是否存在,直到找不到为止,这段连续序列的长度为 y - xy 是第一个不存在的元素)。

三、代码逐行解析

Java 解法

java

class Solution {
    public int longestConsecutive(int[] nums) {
        // 1. 创建HashSet存储所有元素(去重+O(1)查找)
        Set<Integer> st = new HashSet<>();
        for (int num : nums) {
            st.add(num);
        }
        
        // 2. 记录最长连续序列的长度(初始为0,空数组时直接返回0)
        int ans = 0;
        
        // 3. 遍历集合中的每个元素
        for (int x : st) {
            // 3.1 判断x是否是连续序列的起点:若x-1存在,说明x不是起点,跳过
            if (st.contains(x - 1)) {
                continue;
            }
            
            // 3.2 若x是起点,向后延伸找最长连续序列
            int y = x + 1; // 从x的下一个元素开始检查
            while (st.contains(y)) { // 只要y存在,就继续向后找
                y++;
            }
            
            // 3.3 计算当前连续序列的长度(y - x),更新最长长度
            ans = Math.max(ans, y - x);
        }
        
        // 4. 返回最长连续序列的长度
        return ans;
    }
}

Python 解法

python

from typing import List

class Solution:
    def longestConsecutive(self, nums: List[int]) -> int:
        # 1. 创建集合存储所有元素(去重+O(1)查找)
        num_set = set(nums)  # Python中可以直接将列表转为集合
        
        # 2. 记录最长连续序列的长度
        max_length = 0
        
        # 3. 遍历集合中的每个元素
        for num in num_set:
            # 3.1 判断num是否是连续序列的起点:若num-1存在,说明num不是起点,跳过
            if num - 1 in num_set:
                continue
            
            # 3.2 若num是起点,向后延伸找最长连续序列
            current_num = num
            current_length = 1
            
            # 只要current_num+1在集合中,就继续向后找
            while current_num + 1 in num_set:
                current_num += 1
                current_length += 1
            
            # 3.3 更新最长长度
            max_length = max(max_length, current_length)
        
        # 4. 返回最长连续序列的长度
        return max_length

四、Java 与 Python 语法对比

1. 集合创建与使用

操作JavaPython
创建集合Set<Integer> set = new HashSet<>();num_set = set() 或 num_set = {1, 2, 3}
添加元素set.add(num)num_set.add(num)
检查元素是否存在set.contains(num)num in num_set
遍历集合for (int x : set) { ... }for num in num_set:
集合大小set.size()len(num_set)

2. 循环与条件判断

操作JavaPython
for-each 循环for (int num : nums) { ... }for num in nums:
while 循环while (condition) { ... }while condition:
跳过当前迭代continue;continue
最大值函数Math.max(a, b)max(a, b)

3. 类型声明与转换

操作JavaPython
类型声明int[] numsnums: List[int]
列表转集合需要循环添加set(nums)
返回类型public int longestConsecutive(...)def longestConsecutive(...) -> int:

五、实例演示

以测试用例 nums = [100, 4, 200, 1, 3, 2] 为例,演示执行过程:

Java 执行过程:

  1. 初始状态:创建集合 st = {100, 4, 200, 1, 3, 2}ans = 0

  2. 遍历元素

    • x = 100:检查 x-1 = 99 是否存在?否(是起点)。y = 101,集合中无 101,长度 101-100 = 1 → ans = 1

    • x = 4:检查 x-1 = 3 是否存在?是(3 在集合中)→ 跳过

    • x = 200:检查 x-1 = 199 是否存在?否(是起点)。y = 201,无 → 长度 1 → ans 仍为 1

    • x = 1:检查 x-1 = 0 是否存在?否(是起点)。y = 2(存在)→ y = 3(存在)→ y = 4(存在)→ y = 5(不存在)。长度 5-1 = 4 → ans = 4

    • x = 3:检查 x-1 = 2 是否存在?是 → 跳过

    • x = 2:检查 x-1 = 1 是否存在?是 → 跳过

  3. 返回结果ans = 4

Python 执行过程:

  1. 初始状态:创建集合 num_set = {100, 4, 200, 1, 3, 2}max_length = 0

  2. 遍历元素

    • num = 100:检查 99 in num_set?否(是起点)。current_num = 100current_length = 1;检查 101 in num_set?否 → max_length = 1

    • num = 4:检查 3 in num_set?是 → 跳过

    • num = 200:检查 199 in num_set?否(是起点)。current_num = 200current_length = 1;检查 201 in num_set?否 → max_length = 1

    • num = 1:检查 0 in num_set?否(是起点)。current_num = 1current_length = 1;检查 2 in num_set?是 → current_num = 2current_length = 2;检查 3 in num_set?是 → current_num = 3current_length = 3;检查 4 in num_set?是 → current_num = 4current_length = 4;检查 5 in num_set?否 → max_length = 4

    • num = 3:检查 2 in num_set?是 → 跳过

    • num = 2:检查 1 in num_set?是 → 跳过

  3. 返回结果max_length = 4

六、复杂度分析

时间复杂度:O(n)

  • 每个元素最多被访问两次:

    • 一次是在遍历集合时作为 x 被检查

    • 一次是在 while 循环中作为延伸的元素被访问

  • 例如序列 [1, 2, 3, 4] 中:

    • 1 被检查一次(作为起点)

    • 2、3、4 在 while 循环中访问一次

  • 整体遍历次数为 O(n)

空间复杂度:O(n)

  • 哈希集合需要存储所有元素

  • 最坏情况下(无重复元素)空间为 O(n)

七、优化技巧与变体

1. 使用更简洁的 Python 写法

python

class Solution:
    def longestConsecutive(self, nums: List[int]) -> int:
        num_set = set(nums)
        max_length = 0
        
        for num in num_set:
            # 只有当num是序列起点时才计算
            if num - 1 not in num_set:
                current_num = num
                current_length = 1
                
                while current_num + 1 in num_set:
                    current_num += 1
                    current_length += 1
                
                max_length = max(max_length, current_length)
        
        return max_length

2. 使用字典记录边界长度(优化延伸操作)

python

class Solution:
    def longestConsecutive(self, nums: List[int]) -> int:
        # 使用字典记录每个数字所在的连续序列长度
        num_dict = {}
        max_length = 0
        
        for num in nums:
            if num in num_dict:
                continue
                
            # 获取左右相邻数字的序列长度
            left_length = num_dict.get(num - 1, 0)
            right_length = num_dict.get(num + 1, 0)
            
            # 当前数字所在的序列长度
            current_length = left_length + right_length + 1
            
            # 更新当前数字和序列边界的长度
            num_dict[num] = current_length
            num_dict[num - left_length] = current_length
            num_dict[num + right_length] = current_length
            
            max_length = max(max_length, current_length)
        
        return max_length

复杂度分析

  • 时间复杂度:O(n),每个元素只处理一次

  • 空间复杂度:O(n),字典存储所有元素

八、常用函数积累

Java 常用函数

java

// 集合操作
Set<Integer> set = new HashSet<>();
set.add(num);                    // 添加元素
set.contains(num);               // 检查元素是否存在
set.size();                      // 获取集合大小
set.isEmpty();                   // 检查集合是否为空
set.remove(num);                 // 删除元素

// 数组转集合
Set<Integer> set = new HashSet<>(Arrays.asList(1, 2, 3));

// 数学函数
Math.max(a, b);                  // 返回最大值
Math.min(a, b);                  // 返回最小值
Math.abs(num);                   // 返回绝对值

Python 常用函数

python

# 集合操作
num_set = set()
num_set.add(num)                 # 添加元素
num in num_set                   # 检查元素是否存在
len(num_set)                     # 获取集合大小
num_set.remove(num)              # 删除元素(元素不存在会报错)
num_set.discard(num)             # 删除元素(元素不存在不会报错)

# 列表转集合
num_set = set([1, 2, 3])        # 方法1
num_set = {1, 2, 3}             # 方法2(直接创建)

# 内置函数
max(a, b)                        # 返回最大值
min(a, b)                        # 返回最小值
abs(num)                         # 返回绝对值

# 字典操作
num_dict = {}
num_dict.get(key, default)       # 获取值,不存在时返回默认值
num_dict[key] = value            # 设置值
key in num_dict                  # 检查键是否存在

九、总结

核心要点

  1. 哈希集合是关键:利用 HashSet 的 O(1) 查找特性,将查找时间从 O(n) 优化到 O(1)

  2. 避免重复计算:只从每个连续序列的起点开始计算,避免对同一序列的中间元素重复计算

  3. 时间复杂度优化:通过"只处理起点"的策略,确保每个元素最多被访问两次,实现 O(n) 时间复杂度

面试常见问题

  1. 为什么使用 HashSet 而不是先排序?

    • 排序的时间复杂度是 O(n log n),而 HashSet 解法可以达到 O(n)

  2. 为什么只从起点开始计算?

    • 避免重复计算同一序列。如果从中间元素开始计算,会多次计算同一序列的不同部分

  3. 如何处理空数组?

    • 算法天然处理空数组:集合为空时,循环不会执行,直接返回初始值 0

  4. 如果有重复元素怎么办?

    • HashSet 会自动去重,重复元素不影响结果

  5. 这个解法在什么情况下会退化为 O(n²)?

    • 正常情况下不会。每个元素最多被访问两次,所以是 O(n)

    • 如果所有元素都是连续的(如 [1, 2, 3, ..., n]),while 循环会遍历整个序列,但每个元素仍然只被访问一次

扩展思考

这个问题的解法展示了"空间换时间"的经典思想,通过使用额外的哈希集合存储数据,将查找时间从线性优化到常数。类似的思想在解决很多算法问题时都非常有用,例如:

  • 两数之和问题(使用哈希表)

  • 字母异位词分组(使用哈希表存储排序后的字符串)

  • 寻找重复元素(使用哈希集合)

掌握这种思想,能够帮助你在面试中快速识别并解决需要优化查找时间的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值