这道LeetCode题目看似简单,实则暗藏玄机。作为一名刷过300+题的算法工程师,我第一次看到这个题目时也陷入了误区。题目要求我们找出未排序数组中数字连续的最长序列长度,并且必须在O(n)时间内完成。这就像在一堆散落的扑克牌中,找出数字连续的顺子,但牌的顺序完全打乱,而且只能用有限次翻看。
问题的难点主要体现在三个方面:
提示:很多同学第一反应是先排序再处理,这在面试中会被直接淘汰。面试官设置O(n)限制就是为了考察对数据结构特性的理解。
最直观的解法是对每个数字,检查其+1、+2...是否存在于数组中,记录最大长度。这种方法时间复杂度为O(n²),当n=10^5时必然超时。
python复制# 伪代码示意(不可取)
max_len = 0
for num in nums:
current_num = num
current_len = 1
while (current_num + 1) in nums:
current_num += 1
current_len += 1
max_len = max(max_len, current_len)
突破点在于利用哈希集合(O(1)查询)快速判断数字是否存在。我们可以:
关键洞察:一个数字x如果是序列起点,那么x-1必定不在集合中。这样就能避免重复统计。
该算法能保证:
python复制def longestConsecutive(nums):
num_set = set(nums)
max_len = 0
for num in num_set:
# 只处理可能是序列起点的数字
if num - 1 not in num_set:
current_num = num
current_len = 1
# 向后扩展序列
while current_num + 1 in num_set:
current_num += 1
current_len += 1
max_len = max(max_len, current_len)
return max_len
num_set = set(nums):去重并实现O(1)查询if num - 1 not in num_set:识别序列起点的核心判断while current_num + 1 in num_set:向后扩展序列直到中断以nums = [100,4,200,1,3,2]为例:
错误1:未去重导致重复计算
python复制# 错误:直接遍历nums而非num_set
for num in nums: # 可能重复处理相同数字
if num - 1 not in nums: # 线性查询,O(n)时间
...
错误2:错误判断序列起点
python复制# 错误:仅判断num是数组最小值
if num == min(nums): # 获取最小值需要O(n)时间
...
当面试官问及此题时,建议回答流程:
这种算法思想可用于:
如果要求返回最长序列本身而不仅是长度,该如何修改算法?
解决方案:在扩展序列时记录序列元素,当发现更长序列时更新结果。需要额外O(k)空间存储当前序列(k为最长序列长度)。
python复制def longestConsecutiveSequence(nums):
num_set = set(nums)
max_seq = []
for num in num_set:
if num - 1 not in num_set:
current_num = num
current_seq = [current_num]
while current_num + 1 in num_set:
current_num += 1
current_seq.append(current_num)
if len(current_seq) > len(max_seq):
max_seq = current_seq
return max_seq
java复制class Solution {
public int longestConsecutive(int[] nums) {
Set<Integer> numSet = new HashSet<>();
for (int num : nums) {
numSet.add(num);
}
int maxLen = 0;
for (int num : numSet) {
if (!numSet.contains(num - 1)) {
int currentNum = num;
int currentLen = 1;
while (numSet.contains(currentNum + 1)) {
currentNum += 1;
currentLen += 1;
}
maxLen = Math.max(maxLen, currentLen);
}
}
return maxLen;
}
}
注意点:
cpp复制class Solution {
public:
int longestConsecutive(vector<int>& nums) {
unordered_set<int> numSet(nums.begin(), nums.end());
int max_len = 0;
for (int num : numSet) {
if (numSet.find(num - 1) == numSet.end()) {
int current_num = num;
int current_len = 1;
while (numSet.find(current_num + 1) != numSet.end()) {
++current_num;
++current_len;
}
max_len = max(max_len, current_len);
}
}
return max_len;
}
};
优化点:
这个问题也可以用并查集(Disjoint Set Union)解决:
时间复杂度:O(nα(n)),其中α是反阿克曼函数,接近常数但理论复杂度略高于哈希解法。
从信息论角度看,该算法巧妙地利用了数字连续性的局部特性:
在实际工程中选择算法时需要考虑:
对于特别大的数组(1e7以上级别):
理论上可以将数组分片,分别处理后再合并结果:
在LeetCode测试平台上:
这道题的解法体现了几个重要的算法设计思想:
这些思想在解决其他算法问题时也非常有用,比如图论中的邻接表存储、动态规划中的记忆化技术等。理解这道题的解法后,可以尝试解决LeetCode 41.缺失的第一个正数,会发现有异曲同工之妙