128. Longest Consecutive Sequence - Explanation
Description
Given an array of integers nums, return the length of the longest consecutive sequence of elements that can be formed.
A consecutive sequence is a sequence of elements in which each element is exactly 1 greater than the previous element. The elements do not have to be consecutive in the original array.
You must write an algorithm that runs in O(n) time.
Example 1:
Input: nums = [2,20,4,10,3,4,5]
Output: 4Explanation: The longest consecutive sequence is [2, 3, 4, 5].
Example 2:
Input: nums = [0,3,2,5,4,6,1,1]
Output: 7Constraints:
0 <= nums.length <= 100,000-10^9 <= nums[i] <= 10^9
Topics
Recommended Time & Space Complexity
You should aim for a solution as good or better than O(n) time and O(n) space, where n is the size of the input array.
Hint 1
A brute force solution would be to consider every element from the array as the start of the sequence and count the length of the sequence formed with that starting element. This would be an O(n^2) solution. Can you think of a better way?
Hint 2
Is there any way to identify the start of a sequence? For example, in [1, 2, 3, 10, 11, 12], only 1 and 10 are the beginning of a sequence. Instead of trying to form a sequence for every number, we should only consider numbers like 1 and 10.
Hint 3
We can consider a number num as the start of a sequence if and only if num - 1 does not exist in the given array. We iterate through the array and only start building the sequence if it is the start of a sequence. This avoids repeated work. We can use a hash set for O(1) lookups by converting the array to a hash set.
Prerequisites
Before attempting this problem, you should be comfortable with:
- Hash Sets - O(1) average lookup time and membership testing for efficient sequence detection
- Hash Maps - Storing and updating sequence boundary lengths for the union-find-like approach
- Sorting - Understanding how sorting groups consecutive numbers together
1. Brute Force
Intuition
A consecutive sequence grows by checking whether the next number (num + 1, num + 2, …) exists in the set.
The brute-force approach simply starts from every number in the list and tries to extend a consecutive streak as far as possible.
For each number, we repeatedly check if the next number exists, increasing the streak length until the sequence breaks.
Even though this method works, it does unnecessary repeated work because many sequences get recomputed multiple times.
Algorithm
- Convert the input list to a set for O(1) lookups.
- Initialize
resto store the maximum streak length. - For each number
numin the original list:- Start a new streak count at 0.
- Set
curr = num. - While
currexists in the set:- Increase the streak count.
- Move to the next number (
curr += 1).
- Update
reswith the longest streak found so far.
- Return
resafter checking all numbers.
Time & Space Complexity
- Time complexity:
- Space complexity:
2. Sorting
Intuition
If we sort the numbers first, then all consecutive values will appear next to each other.
This makes it easy to walk through the sorted list and count how long each consecutive sequence is.
We simply move forward while the current number matches the expected next value in the sequence.
Duplicates don’t affect the result—they are just skipped—while gaps reset the streak count.
This approach is simpler and more organized than the brute force method because sorting places all potential sequences in order.
Algorithm
- If the input list is empty, return
0. - Sort the array in non-decreasing order.
- Initialize:
resto track the longest streak,curras the first number,streakas0,- index
i = 0.
- While
iis within bounds:- If
nums[i]does not matchcurr, reset:curr = nums[i]streak = 0
- Skip over all duplicates of
currby advancingiwhilenums[i] == curr. - Increase
streakby1since we found the expected number. - Increase
currby1to expect the next number in the sequence. - Update
reswith the maximum streak found so far.
- If
- Return
resafter scanning the entire list.
class Solution:
def longestConsecutive(self, nums: List[int]) -> int:
if not nums:
return 0
res = 0
nums.sort()
curr, streak = nums[0], 0
i = 0
while i < len(nums):
if curr != nums[i]:
curr = nums[i]
streak = 0
while i < len(nums) and nums[i] == curr:
i += 1
streak += 1
curr += 1
res = max(res, streak)
return resTime & Space Complexity
- Time complexity:
- Space complexity: or depending on the sorting algorithm.
3. Hash Set
Intuition
To avoid repeatedly recounting the same sequences, we only want to start counting when we find the beginning of a consecutive sequence.
A number is the start of a sequence if num - 1 is not in the set.
This guarantees that each consecutive sequence is counted exactly once.
Once we identify such a starting number, we simply keep checking if num + 1, num + 2, … exist in the set and extend the streak as far as possible.
This makes the solution efficient and clean because each number contributes to the sequence only one time.
Algorithm
- Convert the list into a set
numSetfor O(1) lookups. - Initialize
longestto track the length of the longest consecutive sequence. - For each number
numinnumSet:- Check if
num - 1is not in the set:- If true,
numis the start of a sequence. - Initialize
length = 1. - While
num + lengthexists in the set, increaselength.
- If true,
- Update
longestwith the maximum length found.
- Check if
- Return
longestafter scanning all numbers.
Time & Space Complexity
- Time complexity:
- Space complexity:
4. Hash Map
Intuition
When we place a new number into the map, it may connect two existing sequences or extend one of them.
Instead of scanning forward or backward, we only look at the lengths stored at the neighbors:
mp[num - 1]gives the length of the sequence ending right beforenummp[num + 1]gives the length of the sequence starting right afternum
By adding these together and including the current number, we know the total length of the new merged sequence.
We then update the left boundary and right boundary of this sequence so the correct length can be retrieved later.
This keeps the whole operation very efficient and avoids repeated work.
Algorithm
- Create a hash map
mpthat stores sequence lengths at boundary positions. - Initialize
res = 0to store the longest sequence found. - For each number
numin the input:- If
numis already inmp, skip it. - Compute the new sequence length:
length = mp[num - 1] + mp[num + 1] + 1
- Store this length at
num. - Update the boundaries:
- Left boundary:
mp[num - mp[num - 1]] = length - Right boundary:
mp[num + mp[num + 1]] = length
- Left boundary:
- Update
resto keep track of the longest sequence.
- If
- Return
resafter processing all numbers.
class Solution:
def longestConsecutive(self, nums: List[int]) -> int:
mp = defaultdict(int)
res = 0
for num in nums:
if not mp[num]:
mp[num] = mp[num - 1] + mp[num + 1] + 1
mp[num - mp[num - 1]] = mp[num]
mp[num + mp[num + 1]] = mp[num]
res = max(res, mp[num])
return resTime & Space Complexity
- Time complexity:
- Space complexity:
Common Pitfalls
Starting a Sequence from Every Number
A common inefficiency is to start counting a sequence from every number in the array, which leads to O(n^2) time complexity. The key optimization is to only start counting from numbers that are the beginning of a sequence (i.e., num - 1 is not in the set).
Not Handling Duplicates Properly
The input array may contain duplicate values. Using a set automatically handles this, but if you iterate over the original array instead of the set, you may process the same sequence multiple times, wasting computation.
Forgetting to Handle Empty Input
When the input array is empty, the longest consecutive sequence has length 0. Some implementations that assume at least one element exists may fail or return incorrect results for this edge case.
Sign in to join the discussion