1. 问题解析与解题思路
LeetCode 128题"最长连续序列"是一个经典的数组处理问题,题目要求给定一个未排序的整数数组nums,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。例如,给定[100, 4, 200, 1, 3, 2],最长连续序列是[1, 2, 3, 4],返回长度4。
这个问题的难点在于如何在O(n)时间复杂度内解决。最直观的排序解法需要O(nlogn)时间,不符合题目对线性的要求。我们需要利用哈希集合的特性来优化查找过程。
1.1 核心算法选择
哈希集合(HashSet)是这个问题的关键数据结构。它的O(1)时间复杂度的查找能力让我们可以快速判断一个数字是否存在。基本思路是:
- 将所有数字存入哈希集合实现去重和快速查找
- 遍历数组,对每个数字检查它是否是某个连续序列的起点
- 如果是起点,则向后扩展序列并记录长度
- 最终返回找到的最大长度
关键技巧:判断一个数字是否是序列起点,只需检查num-1是否存在于集合中。如果不存在,则num是一个序列起点。
2. 详细实现与代码解析
2.1 基础实现版本
python复制def longestConsecutive(nums):
num_set = set(nums)
max_length = 0
for num in num_set:
# 检查是否是序列起点
if num - 1 not in num_set:
current_num = num
current_length = 1
# 向后扩展序列
while current_num + 1 in num_set:
current_num += 1
current_length += 1
max_length = max(max_length, current_length)
return max_length
这个实现的时间复杂度是O(n),因为每个数字最多被访问两次(一次在外部循环,一次在内部while循环)。空间复杂度是O(n),用于存储哈希集合。
2.2 优化技巧与实现细节
- 去重处理:直接使用set(nums)自动去重,避免重复计算
- 提前终止:当剩余未检查数字数量小于当前max_length时,可以提前结束
- 并行扩展:可以同时向前和向后扩展序列,但会增加代码复杂度
优化后的实现:
python复制def longestConsecutive(nums):
num_set = set(nums)
max_length = 0
for num in num_set:
# 提前终止条件
if len(num_set) <= max_length:
break
if num - 1 not in num_set:
current_num = num
current_length = 1
while current_num + 1 in num_set:
current_num += 1
current_length += 1
max_length = max(max_length, current_length)
return max_length
3. 复杂度分析与算法证明
3.1 时间复杂度证明
虽然代码中有嵌套循环,但每个数字最多被访问两次:
- 外部for循环访问一次
- 内部while循环访问一次(当它是某个序列的非起点元素时)
因此总时间复杂度是O(2n) = O(n)
3.2 空间复杂度分析
需要额外的O(n)空间存储哈希集合,因此空间复杂度是O(n)
3.3 正确性证明
算法的正确性基于以下几点:
- 序列起点判定正确:只有当num-1不存在时,num才可能是起点
- 序列扩展完整:从起点开始连续向后扩展,不会遗漏任何连续数字
- 去重处理保证:相同数字不会重复计算
4. 边界条件与测试用例
4.1 常见边界情况
- 空数组输入:应返回0
- 所有元素相同:应返回1
- 已排序数组:应能正确处理
- 逆序数组:应能正确处理
- 大数和小数混合:如[2147483647, -2147483648]
4.2 测试用例示例
python复制test_cases = [
([100, 4, 200, 1, 3, 2], 4),
([0, 3, 7, 2, 5, 8, 4, 6, 0, 1], 9),
([], 0),
([1, 1, 1, 1], 1),
([10, 5, 12, 3, 55, 6, 11, 8, 7, 9, 4], 8)
]
for nums, expected in test_cases:
assert longestConsecutive(nums) == expected
5. 实际应用与变种问题
5.1 实际应用场景
- 数据库ID连续性检查
- 日志时间戳连续性分析
- 用户活跃天数统计
- 生产流水线工序连续性监控
5.2 常见变种问题
- 允许有一个"洞"的最长连续序列
- 需要返回序列本身而不仅是长度
- 二维矩阵中的连续序列问题
- 带权重的连续序列(求最大权重和)
6. 不同语言实现对比
6.1 Java实现
java复制import java.util.HashSet;
import java.util.Set;
public int longestConsecutive(int[] nums) {
Set<Integer> numSet = new HashSet<>();
for (int num : nums) {
numSet.add(num);
}
int maxLength = 0;
for (int num : numSet) {
if (!numSet.contains(num - 1)) {
int currentNum = num;
int currentLength = 1;
while (numSet.contains(currentNum + 1)) {
currentNum++;
currentLength++;
}
maxLength = Math.max(maxLength, currentLength);
}
}
return maxLength;
}
6.2 C++实现
cpp复制#include <unordered_set>
#include <algorithm>
int longestConsecutive(vector<int>& nums) {
unordered_set<int> numSet(nums.begin(), nums.end());
int maxLength = 0;
for (int num : numSet) {
if (numSet.find(num - 1) == numSet.end()) {
int currentNum = num;
int currentLength = 1;
while (numSet.find(currentNum + 1) != numSet.end()) {
currentNum++;
currentLength++;
}
maxLength = max(maxLength, currentLength);
}
}
return maxLength;
}
7. 常见错误与调试技巧
7.1 典型错误实现
错误示例1:未使用哈希集合导致超时
python复制def longestConsecutive(nums):
max_length = 0
for num in nums:
current_num = num
current_length = 1
while current_num + 1 in nums: # 这里用列表的in操作是O(n)
current_num += 1
current_length += 1
max_length = max(max_length, current_length)
return max_length
错误原因:直接使用列表的in操作导致时间复杂度变为O(n²)
7.2 调试技巧
- 打印中间变量:在序列扩展时打印current_num和current_length
- 小规模测试:先用小数组测试基本功能
- 边界测试:专门测试空数组、单元素数组等情况
- 性能测试:用大数组测试是否能在合理时间内完成
8. 算法优化与进阶思考
8.1 并查集解法
这个问题也可以用并查集(Union-Find)来解决,虽然实现更复杂但思想值得了解:
- 初始化每个数字为自己的父节点
- 对于每个数字,将其与num+1和num-1合并
- 最后统计最大的集合大小
8.2 动态规划思路
另一种思路是使用哈希表记录边界信息:
python复制def longestConsecutive(nums):
length_map = {}
max_length = 0
for num in nums:
if num not in length_map:
left = length_map.get(num - 1, 0)
right = length_map.get(num + 1, 0)
current_length = left + right + 1
max_length = max(max_length, current_length)
# 更新边界
length_map[num] = current_length
length_map[num - left] = current_length
length_map[num + right] = current_length
return max_length
这种方法同样能达到O(n)时间复杂度,但空间使用略高。
9. 面试技巧与答题要点
9.1 面试考察重点
- 对哈希表特性的理解与应用能力
- 算法优化意识(从O(nlogn)到O(n))
- 边界条件处理能力
- 代码实现规范性
9.2 回答策略
- 先说明暴力解法及其缺点
- 提出哈希集合优化思路
- 重点解释如何判断序列起点
- 讨论时间/空间复杂度
- 主动提出测试用例
9.3 常见面试问题
Q: 为什么你的算法是O(n)时间复杂度?
A: 因为每个数字最多被访问两次...
Q: 如何处理有重复数字的情况?
A: 使用集合自动去重...
Q: 如果内存有限不能使用O(n)额外空间怎么办?
A: 可以先排序然后用O(1)空间解决,但时间会变为O(nlogn)...
10. 实际工程中的注意事项
- 大数据量处理:当数组非常大时,需要考虑内存使用情况
- 并行化处理:可以将数组分片并行处理,最后合并结果
- 流式处理:如果数据是流式输入的,需要调整算法
- 持久化存储:对于频繁查询的场景,可以预处理后存储结果
在真实工程实现中,可能还需要考虑:
- 数字范围是否超过整数范围
- 是否需要处理浮点数
- 是否允许修改原数组
- 是否需要支持动态增减元素后的实时查询