Appearance
实战:最长连续序列
这道题要求 O(n) 时间复杂度,排序是 O(n log n),不符合要求。怎么办?
哈希表的 O(1) 查找能力是关键。
题目描述
LeetCode 128. 最长连续序列
给定一个未排序的整数数组 nums,找出数字连续的最长序列的长度。
请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
示例:
输入:nums = [100, 4, 200, 1, 3, 2]
输出:4
解释:最长连续序列是 [1, 2, 3, 4],长度为 4暴力思路
最直接的想法:排序后遍历,统计连续元素的长度。
javascript
function longestConsecutive(nums) {
if (nums.length === 0) return 0;
nums.sort((a, b) => a - b);
let maxLen = 1, currentLen = 1;
for (let i = 1; i < nums.length; i++) {
if (nums[i] === nums[i-1] + 1) {
currentLen++;
maxLen = Math.max(maxLen, currentLen);
} else if (nums[i] !== nums[i-1]) {
currentLen = 1;
}
}
return maxLen;
}时间复杂度 O(n log n),不符合要求。
哈希表解法
核心思想:用 Set 存储所有数字,对于每个数字,只有当它是序列的起点时才开始计数。
怎么判断是不是起点?如果 num - 1 不存在,那 num 就是某个序列的起点。
javascript
function longestConsecutive(nums) {
if (nums.length === 0) return 0;
const numSet = new Set(nums);
let maxLength = 0;
for (const num of numSet) {
// 只从序列起点开始计数
if (!numSet.has(num - 1)) {
let currentNum = num;
let currentLength = 1;
// 向后查找连续数字
while (numSet.has(currentNum + 1)) {
currentNum++;
currentLength++;
}
maxLength = Math.max(maxLength, currentLength);
}
}
return maxLength;
}执行过程
nums = [100, 4, 200, 1, 3, 2]
numSet = {100, 4, 200, 1, 3, 2}
遍历 numSet:
num = 100:
99 不在 Set 中 → 100 是起点
查找 101? 不存在
序列 [100],长度 1
num = 4:
3 在 Set 中 → 4 不是起点,跳过
num = 200:
199 不在 Set 中 → 200 是起点
查找 201? 不存在
序列 [200],长度 1
num = 1:
0 不在 Set 中 → 1 是起点
查找 2? 存在 → currentNum = 2, length = 2
查找 3? 存在 → currentNum = 3, length = 3
查找 4? 存在 → currentNum = 4, length = 4
查找 5? 不存在
序列 [1,2,3,4],长度 4
num = 3:
2 在 Set 中 → 3 不是起点,跳过
num = 2:
1 在 Set 中 → 2 不是起点,跳过
最大长度:4为什么是 O(n)?
你可能会问:外层循环是 O(n),内层 while 循环不也是遍历吗?总时间不是 O(n²)?
关键洞察:每个数字最多被访问两次。
- 外层循环遍历时访问一次
- while 循环中被访问一次(作为某个序列的一部分)
比如数字 3:
- 在外层遍历到 3 时,判断 2 存在,跳过
- 在 1 的 while 循环中,被访问一次
总操作次数 ≤ 2n,所以是 O(n)。
这是一种常见的复杂度分析技巧:均摊分析。
复杂度
- 时间:O(n)
- 空间:O(n),存储所有数字的 Set
为什么一定要判断起点?
如果不判断起点会怎样?
javascript
// 错误做法
for (const num of numSet) {
let currentNum = num;
let currentLength = 1;
while (numSet.has(currentNum + 1)) {
currentNum++;
currentLength++;
}
maxLength = Math.max(maxLength, currentLength);
}对于 nums = [1, 2, 3, 4]:
- 遍历到 1,计数 1→2→3→4,长度 4
- 遍历到 2,计数 2→3→4,长度 3(重复!)
- 遍历到 3,计数 3→4,长度 2(重复!)
- 遍历到 4,长度 1(重复!)
时间复杂度退化为 O(n²)!
判断起点的目的就是避免重复计数。
本章小结
最长连续序列展示了哈希表的一个重要用法——O(1) 判断存在性:
- 用 Set 存储所有数字,查找 O(1)
- 只从序列起点开始计数,避免重复
- 均摊分析证明总时间 O(n)
这道题的精妙之处在于"起点判断"这个优化,它把看似 O(n²) 的算法降为 O(n)。