力扣解题-最长连续序列
题目: 给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
示例 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 <= nums.length <= 104 -109 <= nums[i] <= 109 进阶:可以设计并实现时间复杂度为 O(n) 的解决方案吗?
注意:本题与主站 128 题相同: leetcode.cn/problems/lo…
Related Topics 并查集 数组 哈希表
第一次解答(失败,超时)
解题思路
核心方法:哈希表存储所有元素 + 遍历原数组逐个向前查找连续序列,利用哈希表O(1)的查找效率,尝试通过遍历每个元素并向前查找num-1、num-2...的方式,统计以当前元素为终点的连续序列长度,最终取所有长度的最大值。
具体步骤:
- 初始化
HashMap,将数组中所有元素以「数值-数值」的键值对形式存入,仅用于快速判断某个数值是否存在。 - 初始化结果变量
res为0,用于记录最长连续序列的长度。 - 遍历原数组
nums的每个元素nums[i]: a. 初始化当前序列长度curentRes为0,当前查找值value为nums[i]。 b. 通过while循环不断查找哈希表中是否包含value,若包含则当前长度加1,且value自减1(向前查找连续数)。 c. 循环结束后,更新res为res和curentRes中的较大值。 - 遍历完成后返回
res。
失败原因分析
该解法超时的核心原因是存在大量重复且无效的计算,时间复杂度退化为O(n²),无法满足题目数据规模要求,具体问题点:
- 未去重导致重复遍历:哈希表存储了所有元素,但遍历的是原数组而非去重后的集合,若数组存在重复数值(如示例2中的0),会对同一个数值多次执行完整的连续序列查找,产生大量冗余计算。
- 未跳过非序列起点元素:对数组中每个元素都执行向前查找的完整循环,即使该元素是某连续序列的中间节点(如序列[1,2,3,4]中的2、3、4),也会重新从该元素开始查找1、0...,同一个连续序列会被多次遍历,最坏情况下(如数组是连续递增序列),时间复杂度会达到O(n²),对于
10^4级别的数组,直接触发超时。 - 哈希表无意义存储:以
num-num为键值对存储,仅用了哈希表的containsKey功能,完全可替换为更轻量的HashSet,属于不必要的内存开销。
public int longestConsecutive(int[] nums) {
Map<Integer, Integer> map = new HashMap<>();
for(int num:nums){
map.put(num,num);
}
int res = 0;
for(int i=0;i<nums.length;i++){
int curentRes=0;
int value=nums[i];
while(map.containsKey(value)){
curentRes=curentRes+1;
value=value-1;
}
res=Math.max(res,curentRes);
}
return res;
}
第二次解答
解题思路
核心方法:HashSet去重 + 仅从连续序列起点开始向后遍历,解决了第一次解答的重复计算问题,确保每个元素仅被访问有限次,将时间复杂度优化至O(n),同时利用Set的轻量特性减少内存开销。
具体步骤:
- 初始化
HashSet,将数组中所有元素存入集合,自动完成去重,避免重复数值的无效计算,同时保留O(1)的查找效率。 - 初始化最大长度变量
maxLen为0,用于记录最长连续序列的长度。 - 遍历去重后的Set而非原数组,对每个数值
num执行核心判断: a. 先判断Set中是否不包含num - 1,该条件是连续序列起点的唯一判定依据(若包含num-1,说明num是某连续序列的中间节点,无需处理)。 b. 若num是序列起点,初始化当前序列数值currentNum为num,当前序列长度currentLen为1(起点自身占1个长度)。 c. 通过while循环向后查找连续数(currentNum + 1),若Set中包含则currentNum自增1、currentLen加1,直到无法找到下一个连续数。 d. 循环结束后,更新maxLen为maxLen和currentLen中的较大值。 - 遍历完成后,返回
maxLen即为最长连续序列的长度。
核心优化点说明
- Set去重:消除原数组重复数值的重复计算,确保每个数值仅被处理一次。
- 起点判定:通过
!set.contains(num - 1)跳过所有连续序列的中间/终点元素,仅对每个连续序列的起点执行一次向后遍历,从根本上解决了第一次解答的重复遍历问题。 - 向后查找更高效:贴合连续序列的特性,从起点向后查找
num+1,每个元素最多被访问两次(一次是遍历到自身,一次是作为连续数被起点查找),整体时间复杂度稳定为O(n)。 - 轻量存储:用
HashSet替代HashMap,仅存储数值完成存在性判断,减少内存开销。
执行耗时:13 ms,击败了95.38% 的Java用户 内存消耗:58.9 MB,击败了45.88% 的Java用户
public int longestConsecutive(int[] nums) {
Set<Integer> set = new HashSet<>();
for(int num:nums){
set.add(num);
}
int maxLen = 0;
for (int num : set) {
if (!set.contains(num - 1)) {
int currentNum = num;
int currentLen = 1;
while (set.contains(currentNum + 1)) {
currentNum++;
currentLen++;
}
maxLen = Math.max(maxLen, currentLen);
}
}
return maxLen;
}
总结
- 第一次解答失败的核心是无去重+无起点判定,导致大量重复计算,时间复杂度退化为O(n²),触发超时;同时存在哈希表无意义存储的内存开销。
- 第二次解答的核心优化是HashSet去重和仅从序列起点遍历,从根本上消除了重复计算,将时间复杂度优化至O(n),满足题目进阶要求。
- 两次解法均利用了哈希结构的O(1)查找效率,第二次通过精准判定遍历起点的小技巧,实现了从超时解法到高效解法的转变,是本题的解题关键。
- 本题的核心思路是空间换时间,用哈希集合的O(n)空间开销,换取遍历效率的质的提升,同时通过逻辑优化避免冗余计算。