给定一个未排序的整数数组 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))。关键思路是:
- 使用HashSet存储所有数字:这样可以在O(1)时间内查找任意数字是否存在
- 找到每个连续序列的起始点:对于数字num,如果num-1不存在,说明num是某个连续序列的起始点
- 从起始点开始计算序列长度:从起始点开始,依次查找num+1, num+2, num+3...直到找不到为止
- 记录最长序列长度:在所有序列中找到最长的那个
这样做的巧妙之处在于:虽然看起来有嵌套循环,但每个数字最多被访问两次(一次在外层循环判断是否为起始点,一次在内层循环作为某个序列的一部分),所以总时间复杂度仍然是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:存储键值对,用于建立映射关系
在这个问题中的分析
我们的需求
在最长连续序列问题中,我们只需要:
- 快速查找某个数字是否存在于数组中
- 不需要存储任何额外信息
浙公网安备 33010602011771号