128.最长连续序列

Posted on 2025-10-15 13:57  lachesism  阅读(3)  评论(0)    收藏  举报

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

示例 3:

输入:nums = [1,0,1,2]
输出:3

 

提示:

  • 0 <= nums.length <= 105
  • -109 <= nums[i] <= 109

解题思路

要达到O(n)的时间复杂度,不能使用排序方法(排序是O(n log n))。关键思路是:

  1. 使用HashSet存储所有数字:这样可以在O(1)时间内查找任意数字是否存在
  1. 找到每个连续序列的起始点:对于数字num,如果num-1不存在,说明num是某个连续序列的起始点
  1. 从起始点开始计算序列长度:从起始点开始,依次查找num+1, num+2, num+3...直到找不到为止
  1. 记录最长序列长度:在所有序列中找到最长的那个

这样做的巧妙之处在于:虽然看起来有嵌套循环,但每个数字最多被访问两次(一次在外层循环判断是否为起始点,一次在内层循环作为某个序列的一部分),所以总时间复杂度仍然是O(n)。

 

import java.util.*;

public class Solution {
    public int longestConsecutive(int[] nums) {
        // 第一步:处理边界情况
        // 如果数组为空或null,直接返回0
        if (nums == null || nums.length == 0) {
            return 0;
        }
        
        // 第二步:把数组中的所有数字放到HashSet里
        // HashSet的作用:可以在O(1)时间内快速查找某个数字是否存在
        // 去除重复数字,只关心数字存在与否
        Set<Integer> numSet = new HashSet<>();
        
        // for (int num : nums) 是增强for循环的写法
        // 意思是:从nums数组中依次取出每个元素,赋值给变量num
        // 等价于传统的for循环,但更简洁
        for (int num : nums) {
            numSet.add(num);  // 把每个数字都加到HashSet里
        }
        
        // 第三步:准备一个变量记录最长的连续序列长度
        int longestStreak = 0;
        
        // 第四步:遍历HashSet中的每个数字
        // for (int num : numSet) 同样是增强for循环
        // 从numSet中依次取出每个数字进行处理
        for (int num : numSet) {
            
            // 关键判断:这个数字是不是连续序列的"开头"?
            // 判断方法:看看比它小1的数字存不存在
            // 如果num-1不存在,说明num就是某个连续序列的起始点
            // 这样做是为了避免重复计算同一个序列
            if (!numSet.contains(num - 1)) {
                
                // 如果是序列开头,开始计算这个序列的长度
                int currentNum = num;        // 当前正在检查的数字,从开头数字开始
                int currentStreak = 1;       // 当前序列的长度,开头数字算1个
                
                // 从开头数字开始,一直往后找连续的数字
                // numSet.contains(currentNum + 1) 是检查下一个数字存不存在
                // 注意:这里只是检查currentNum + 1,不会改变currentNum的值
                while (numSet.contains(currentNum + 1)) {
                    
                    // 如果下一个数字存在,就移动到下一个数字
                    currentNum++;      // 真正移动:把currentNum增加1
                    currentStreak++;   // 序列长度也增加1
                    
                    // 举例:如果当前是1,检查2存在,就移动到2,长度变成2
                    //      如果当前是2,检查3存在,就移动到3,长度变成3
                    //      如果当前是4,检查5不存在,就退出while循环
                }
                
                // while循环结束后,我们得到了从num开始的完整连续序列长度
                // 现在检查这个序列是不是目前最长的
                // Math.max(a, b) 返回两个数中较大的那个
                // 比较当前记录的最长长度 和 刚找到的序列长度,取较大值
                longestStreak = Math.max(longestStreak, currentStreak);
                
                // 举例:如果之前最长是1,现在找到长度4的序列,就更新为4
                //      如果之前最长是4,现在找到长度2的序列,还是保持4
            }
            // 如果num-1存在,说明num不是序列开头,跳过不处理
            // 这样避免了重复计算同一个序列
        }
        
        // 返回找到的最长连续序列长度
        return longestStreak;
    }
}

/*
完整示例演示:nums = [100, 4, 200, 1, 3, 2]

1. 建立HashSet: {100, 4, 200, 1, 3, 2}

2. 遍历每个数字:

   检查100:
   - contains(99)? false → 100是开头
   - contains(101)? false → 序列[100],长度1
   - longestStreak = max(0, 1) = 1

   检查4:
   - contains(3)? true → 4不是开头,跳过

   检查200:
   - contains(199)? false → 200是开头  
   - contains(201)? false → 序列[200],长度1
   - longestStreak = max(1, 1) = 1

   检查1:
   - contains(0)? false → 1是开头
   - contains(2)? true → 移动到2,长度2
   - contains(3)? true → 移动到3,长度3  
   - contains(4)? true → 移动到4,长度4
   - contains(5)? false → 停止
   - 序列[1,2,3,4],长度4
   - longestStreak = max(1, 4) = 4

   检查3和2: 都不是开头,跳过

3. 返回结果:4
*/

 

今日学到的是
为什么在这个问题中使用 HashSet<Integer> 而不是 HashMap<Integer, Integer>。

核心区别

HashSet:只存储值,用于快速判断元素是否存在
HashMap:存储键值对,用于建立映射关系

在这个问题中的分析

我们的需求

在最长连续序列问题中,我们只需要:

  • 快速查找某个数字是否存在于数组中
  • 不需要存储任何额外信息