力扣hot100之最长连续序列(java版)
1、题目描述
给定一个未排序的整数数组
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
2、思路
第一步:理解题意 —— 什么是“连续序列”?
举个例子:
输入:[100, 4, 200, 1, 3, 2]
输出:4
解释:最长连续序列是 [1, 2, 3, 4],长度为 4。
注意:
不要求原数组中位置连续,只要数值连续。
序列必须是严格递增 +1 的整数序列。
数组可能有重复元素(如
[1,2,2,3]→ 最长是[1,2,3],长度3)。
知识点1:理解“连续序列”的定义
连续序列 = 一组整数,彼此相差1,如 3,4,5,6 —— 这是“公差为1的等差数列”。
第二步:暴力解法是什么?
暴力思路:
对数组排序 →
Arrays.sort(nums)→ O(n log n)遍历排序后的数组,计算连续递增+1的长度 → O(n)
记录最大长度。
这种方式可以做出来,但时间复杂度是 O(n log n),不符合题目要求的 O(n)。
知识点2:排序的时间复杂度下限
比较排序(如快排、归并)最坏/平均都是 O(n log n)。题目要求 O(n),意味着不能排序!
第三步:如何在 O(n) 时间内解决?→ 引入哈希集合(HashSet)
核心思想:
我们不想排序,但又想“快速查找某个数字是否存在”,怎么办?
用 HashSet!它支持 O(1) 平均时间 的插入和查找!
知识点3:哈希表(HashSet)的基本原理
HashSet 是基于哈希表实现的无序、不重复集合。它是一个单列集合,一次存取一个元素,存入和取出的顺序不一致(不是随机的,是有特定的规则)。
插入、删除、查找 平均时间复杂度是 O(1)。
底层是数组 + 链表/红黑树(Java 8+优化)。
适用于“快速判断元素是否存在”的场景。
第一步操作:把所有数字放进 HashSet
//这里所有的数都是整数,因此泛型限制为整型
Set<Integer> numSet = new HashSet<>();//集合的通用遍历方式--增强for循环for (int num : nums) {numSet.add(num);//hashSet中添加元素的方法}现在,我们可以用 numSet.contains(x) 在 O(1) 时间内判断 x 是否存在!
第四步:如何避免重复计算?---->只从“序列起点”开始!
关键洞察:
如果我们对每个数字都尝试向后扩展(比如对 2 扩展出 3,4,5…),那 1,2,3,4 会被重复计算多次!
我们只应该从“连续序列的起点”开始扩展!
什么是“起点”?
一个数 x 是起点,当且仅当
x - 1不存在于集合中!
比如:
对于 1:0 不存在 → 1 是起点
对于 2:1 存在 → 2 不是起点
对于 100:99 不存在 → 100 是起点
这样,我们就能保证每个连续序列只被计算一次!
知识点4:避免重复计算的思维技巧 —— “只处理代表元”
在很多算法题中(如并查集、图遍历),我们通过“只处理某个特定代表”来避免重复工作。这里是“只处理序列起点”。
第五步:对每个起点,向后扩展,计算长度
算法步骤:
对每个数 num:
如果
num - 1不在集合中 → 它是起点。从
num开始,不断检查num + 1,num + 2, ... 是否在集合中。记录当前序列长度。
更新全局最大长度。
for (int num : numSet) {//用 numSet.contains(x) 在 O(1) 时间内判断 x 是否存在if (!numSet.contains(num - 1)) { // 是起点int currentNum = num;int currentStreak = 1; //用 numSet.contains(x) 在 O(1) 时间内判断 x 是否存在while (numSet.contains(currentNum + 1)) {currentNum++;currentStreak++;} //更新最大连续序列的长度longestStreak = Math.max(longestStreak, currentStreak);}}知识点5:贪心扩展 + 局部最优 → 全局最优
我们“贪心”地从每个起点尽可能向右扩展,局部求出以它为起点的最长序列,然后取全局最大值。
第六步:为什么时间复杂度是 O(n)?
看起来有两层循环,怎么会是 O(n)?
外层循环遍历所有数字 → O(n)
内层 while 循环看起来会重复访问数字 → 但实际上每个数字最多被访问两次:
一次在
for循环中判断是否是起点。一次在某个起点的
while循环中被访问(作为序列中间元素)。
总访问次数 <= 2n → 时间复杂度 O(n)!
知识点6:摊还分析(Amortized Analysis)
虽然某次操作可能耗时较长,但平均到所有操作上,每次操作的“均摊成本”很低。这里是“每个元素最多被访问两次”,所以总复杂度是线性的。
第七步:边界情况处理
//当这个赎罪是空的或者它的成都为0都应该立刻停止运行,直接返回0
if (nums == null || nums.length == 0) {return 0;}知识点7:防御性编程 & 边界条件
任何算法题都要考虑:
空数组
null 输入
单元素
全重复元素
负数、零、极大极小值
完整思路总结
明确目标:找最长的“数值连续”序列,不要求位置连续。
拒绝排序:因为 O(n log n) 不符合要求。
引入 HashSet:用空间换时间,实现 O(1) 查找。
聪明遍历:只从“起点”(x-1 不存在)开始扩展,避免重复。
贪心扩展:对每个起点,向右一直找 x+1, x+2... 直到断掉。
记录最大值:每次扩展完,更新最长长度。
复杂度分析:每个元素最多访问两次 → O(n)。
边界处理:空数组返回0。
3、完整代码实现
方法一:暴力破解(排序+相邻比较)
暴力破解虽然可以解决,但是不符合题意,但是我们可以先用暴力破解来解决此题,然后再想想可不可以把时间复杂度优化。
class Solution{public int longestConsecutive(int[] nums){if(nums == null || nums.length == 0){return 0;}//先对数组进行排序,使用Arrays自带的排序方法Arrays.sort(nums);int maxCount = 0;//记录最长连续序列的长度int currentCount = 1;//当前连续序列的长度//这里需要注意一下:数组的长度表示是nums.length,length表示的是属性//字符串的长度表示是chars.length(),这里表示使用这个length()方法来获取字符串的长度for(int i = 1;i < nums.length;i++){if(nums[i] == nums[i-1]){//如果当前元素和前一个元素相同,则跳过continue;//跳过本次循环,进行下一次循环}else if(nums[i] == nums[i-1] + 1){//如果当前元素是前面一个元素的连续值,则增加当前连续序列的长度currentCount++;}else{//如果当前元素不是前一个元素的连续值,则重置当前连续序列的长度maxCount = Math.max(maxCount,currentCount);currentCount = 1;}}//最后一次更新最大长度maxCount = Math.max(maxCount,currentCount);return maxCount;}
}问题:为什么要从1开始遍历?
因为你要比较 nums[i] 和 nums[i-1],也就是“当前元素”和“前一个元素”。
如果 i = 0,那你就要访问 nums[-1] → 数组越界 ❌
所以必须从 i = 1 开始,这样 i-1 = 0 是合法索引 ✅
方法二:使用哈希表hashSet
class Solution{public int longestConsecutive(int[] nums){//0、先判断边界情况if(nums == null || nums.length == 0){return 0;}//1、把所有的数字都存入到HashSet中,去重+快速查找Set<Integer> numSet = new HashSet<>();for(int num : nums){numSet.add(num);}int longestStreak = 0;//记录最长连续序列的长度//2、迭代器遍历:也就是增强for循环;遍历每个数字,只从‘起点’开始扩展for(int num : numSet){//如果num-1不存在,说明num就是某个连续序列的起点if(!numSet.contains(num -1)){int currentNum = num;//当前正在检查的数字int currentStreak = 1;//当前连续序列的长度//3、从起点开始扩展,向右扩展//这里的循环条件是判断“起点”后的扩展在HashSet中是否存在while(numSet.contains(currentNum + 1)){currentNum++;//每遍历一次就+1,在HashSet中查询有没有连续的序列currentStreak++;//如果有连续的序列就继续寻找,且长度+1}//4、更新全局最大长度longestStreak = Math.max(longestStreak,currentStreak);}}return longestStreak;}
}