#128 - Longest Consecutive Sequence
Longest Consecutive Sequence
- Difficulty: Medium
- Topics: Array, Hash Table, Union Find
- Link: https://leetcode.com/problems/longest-consecutive-sequence/
Problem Description
Given an unsorted array of integers nums
, return the length of the longest consecutive elements sequence.
You must write an algorithm that runs in O(n)
time.
Example 1:
Input: nums = [100,4,200,1,3,2]
Output: 4
Explanation: The longest consecutive elements sequence is [1, 2, 3, 4]. Therefore its length is 4.
Example 2:
Input: nums = [0,3,7,2,5,8,4,6,0,1]
Output: 9
Example 3:
Input: nums = [1,0,1,2]
Output: 3
Constraints:
0 <= nums.length <= 105
-109 <= nums[i] <= 109
Solution
1. Problem Deconstruction
Technical Description:
Given an unsorted array of integers, compute the maximum length of a contiguous interval of integers that appear in the array. A contiguous interval is a set of integers where each element exists in the input. The solution must run in time with as the input size.
Beginner Description:
Imagine you have a bag of numbered tiles. Find the longest unbroken chain of tiles where each number is exactly one more than the previous (like 5, 6, 7). The chain doesn’t need to appear in order in the bag. Solve this efficiently even for huge bags (100,000+ tiles).
Mathematical Description:
Let be the set of input integers. Find:
Constraint Analysis:
- :
- Empty input returns 0.
- solutions fail (1e10 operations at ). Forces design.
- :
- Range too large for array indexing (2e9+1 slots). Requires hash-based lookups.
- Negative numbers and zero handled naturally via sets.
Hidden Edge Cases:
- Duplicates: Ignore duplicates (consecutive sequences require distinct integers).
- Single-element sequences: Minimum sequence length is 1.
- Disjoint sequences: e.g., has (length 5).
- Overflow risks: Large integers don’t affect set lookups (hash collisions negligible).
2. Intuition Scaffolding
Real-World Metaphor:
Like finding the longest unbroken strand of pearls where each pearl is numbered consecutively. Instead of sorting all pearls (slow), quickly check neighbors by dumping pearls into labeled buckets (hash set).
Gaming Analogy:
In a dungeon crawler, teleporters are placed at random integer coordinates. Find the longest path where you can hop between adjacent teleporters (). Optimize by only starting searches at teleporters with no left neighbor.
Math Analogy:
Maximize where is an interval with step size 1 and . Requires efficient membership queries without sorting .
Common Pitfalls:
- Naive brute force: For each number, scan forward until break. Worst-case (e.g., ).
- Sorting: violates requirement.
- Global min/max focus: Longest sequence may not start at global min (e.g., ).
- Duplicate mishandling: Counting duplicates inflates sequence length (e.g., is length 2, not 3).
- Over-engineering with union-find: Unnecessary for linear sequences (simpler set method exists).
3. Approach Encyclopedia
Approach 1: Brute Force (Naive)
Pseudocode:
max_len = 0
for num in nums:
current = num
while current + 1 in nums_set:
current += 1
max_len = max(max_len, current - num + 1)
Complexity Proof:
- Time: where is max sequence length. Worst-case (single sequence).
- Space: for the set.
Visualization:
nums = [1, 2, 3]
1 → 2 → 3 (length=3)
2 → 3 (length=2)
3 → (length=1)
Approach 2: Sorting
Pseudocode:
sort(nums)
remove_duplicates_in_sorted() # Or use set first
max_len, curr_len = 0, 1
for i in range(1, len(nums)):
if nums[i] == nums[i-1] + 1:
curr_len += 1
else:
max_len = max(max_len, curr_len)
curr_len = 1
return max(max_len, curr_len)
Complexity Proof:
- Time: for sorting + scan. Dominated by sort.
- Space: (in-place sort) or (if copying).
Visualization:
Sorted: [1, 2, 3, 100, 200]
Scan: 1→2→3 (break at 100), curr_len=3 → max_len=3.
Approach 3: Set with Sequence Heads (Optimal)
Pseudocode:
nums_set = set(nums)
max_len = 0
for num in nums_set:
if num - 1 not in nums_set: # Head of sequence
curr = num
while curr + 1 in nums_set:
curr += 1
max_len = max(max_len, curr - num + 1)
Complexity Proof:
- Time: — Each element processed twice max (once in loop, once in sequence).
- Space: for the set.
Visualization:
Set: {100, 4, 200, 1, 3, 2}
Heads: 100 (no 99), 200 (no 199), 1 (no 0)
1 → 2 → 3 → 4 (length=4)
Approach 4: Boundary Mapping with Dictionary
Pseudocode:
boundary = {}
max_len = 0
for num in nums:
if num not in boundary:
L = boundary.get(num-1, 0)
R = boundary.get(num+1, 0)
length = 1 + L + R
boundary[num] = length
boundary[num - L] = length # Update left boundary
boundary[num + R] = length # Update right boundary
max_len = max(max_len, length)
Complexity Proof:
- Time: — Each num processed once, dictionary ops .
- Space: — Store boundaries for sequences.
Visualization:
nums = [1, 2, 0, 3]
Step 0: num=1 → L=0, R=0 → length=1 → boundaries{1:1}
Step 1: num=2 → L=boundary[1]=1, R=0 → length=2 → update boundaries{1:2, 2:2}
Step 2: num=0 → L=0, R=boundary[1]=2 → length=3 → update boundaries{0:3, 1:2 → 3? NO, update 0 and 2:}
boundary[0]=3, boundary[2]=3 (and boundary[1] remains, but unused)
Step 3: num=3 → L=boundary[2]=3, R=0 → length=4 → update boundaries{3:4, 0:4, 3:4}
4. Code Deep Dive
Optimal Solution (Set with Heads):
def longestConsecutive(nums):
if not nums:
return 0
num_set = set(nums) # Remove duplicates, O(1) lookups
max_length = 0
for num in num_set: # Iterate unique numbers
if num - 1 not in num_set: # Identify sequence head
current_num = num
current_length = 1
while current_num + 1 in num_set: # Extend sequence
current_num += 1
current_length += 1
max_length = max(max_length, current_length)
return max_length
Edge Case Handling:
- Empty Input:
if not nums
returns 0. - Duplicates: Set removes them (e.g., → set ).
- Example 1:
- Heads: , ,
- Sequence from : (length 4).
- Example 2:
- Heads: (no ), others skipped.
- Sequence: (length 9).
- Example 3:
- Heads: (no ), sequence (length 3).
5. Complexity War Room
Hardware-Aware Analysis:
- At , set uses (fits in L3 cache).
- Single pass with lookups: operations (fast on modern CPUs).
Industry Comparison Table:
Approach | Time | Space | Readability | Interview Viability |
---|---|---|---|---|
Brute Force | 9/10 | ❌ Fails large | ||
Sorting | 8/10 | ⚠️ Not | ||
Set (Optimal) | 10/10 | ✅ Clear & efficient | ||
Boundary Mapping | 7/10 | ✅ Clever but tricky |
6. Pro Mode Extras
Variants:
- Longest Consecutive Sequence in Binary Tree (Leetcode 298):
- Consecutive path along parent-child edges. DFS with parent value tracking.
- Multiple Transactions (Leetcode 123):
- Extend to transactions (dynamic programming with 3D state).
- Longest Arithmetic Subsequence (Leetcode 1027):
- Generalize to arbitrary difference (not just 1).
Interview Cheat Sheet:
- First Step: Clarify “consecutive” = value difference 1 (not array position).
- Key Insight: Only start sequences at heads (no left neighbor) for .
- Mention: Set for lookups and duplicate removal.
- Test: Empty input, duplicates, disjoint sequences, all duplicates.
- Tradeoffs: Set uses space but enables time.