The Problem Statement
Given an unsorted array of integers nums, return the length of the longest consecutive elements sequence.
A consecutive sequence means the numbers in the sequence must be consecutive integers (e.g., [1, 2, 3, 4]), meaning each number is exactly 1 greater than the previous one.
The catch? The elements do not need to be next to each other in the original array, and you must design an algorithm that runs in O(n) time complexity.
Understanding with Examples
Example 1
- Input:
nums = [100, 4, 200, 1, 3, 2] - Output:
4 - Explanation: The longest consecutive elements sequence is
[1, 2, 3, 4]. Its length is4. Notice that even though these numbers are scattered across the array, they form a perfect continuous chain when reordered conceptually.
Example 2
- Input:
nums = [0, 3, 7, 2, 5, 8, 4, 6, 0, 1] - Output:
9 - Explanation: The longest consecutive elements sequence is
[0, 1, 2, 3, 4, 5, 6, 7, 8]. Its length is9. (Note: Duplicate elements like the extra0do not extend the sequence length).
Constraints:
0 <= nums.length <= 105-109 <= nums[i] <= 109
The Brute Force Approach
When encountering this problem for the first time, the most instinctive way to solve it is to explore every possible continuous chain from scratch. Since the array is completely unsorted, we pick each number one by one and check if it can serve as the starting point of our longest consecutive sequence.
1. Intuition
Imagine you have a deck of cards scattered randomly on a table. To find the longest consecutive run of numbers without sorting them, you might pick up a cardβsay, a 4βand then scan the entire table looking for a 5. If you find it, you look for a 6, then a 7, and so on, until a number is missing. You write down how long that chain was. Then, you put those cards back, pick the next card from the table, and repeat the entire process.
By running a linear search across the entire array for the "next" consecutive number over and over again, we can track the maximum streak length found.
2. The Algorithm
To turn this intuition into a formal algorithm, we rely on a helper function to perform linear searches alongside our main tracking loop:
- Create a helper function
arrayContains(arr, target)that loops through the array to check if a specifictargetnumber exists. - In the main function, initialize a variable
longest_streak = 0. If the input array is empty, return0. - Loop through every element
numin thenumsarray:- Set a temporary tracker
current_num = num. - Set a temporary streak counter
current_streak = 1. - Open a while loop that continues running as long as
arrayContains(nums, current_num + 1)returnstrue. - Inside this while loop, increment
current_numby 1 and incrementcurrent_streakby 1.
- Set a temporary tracker
- After exiting the while loop for the current element, update
longest_streakif the current chain is longer:longest_streak = max(longest_streak, current_streak). - Return
longest_streakonce all numbers have been checked.
3. Detailed Dry Run
Array: [3, 1, 4, 2]
π Loop i=0: num = 3
βββ Check 4? π’ YES β‘οΈ [3, 4] (Streak: 2)
βββ Check 5? π΄ NO
π Max Streak = 2
π Loop i=1: num = 1
βββ Check 2? π’ YES β‘οΈ [1, 2]
βββ Check 3? π’ YES β‘οΈ [1, 2, 3]
βββ Check 4? π’ YES β‘οΈ [1, 2, 3, 4] (Streak: 4)
βββ Check 5? π΄ NO
π Max Streak = 4 π₯ (Updated)
π Loop i=2: num = 4
βββ Check 5? π΄ NO β‘οΈ [4] (Streak: 1)
π Max Streak = 4
π Loop i=3: num = 2
βββ Check 3? π’ YES β‘οΈ [2, 3]
βββ Check 4? π’ YES β‘οΈ [2, 3, 4] (Streak: 3)
βββ Check 5? π΄ NO
π Max Streak = 4
π FINAL ANSWER: 4C++ code
class Solution {
private:
// Helper function to do a simple linear search
bool arrayContains(vector<int>& arr, int target) {
for (int x : arr) {
if (x == target) {
return true;
}
}
return false;
}
public:
int longestConsecutive(vector<int>& nums) {
// Edge case: if array is empty, the longest sequence is 0
if (nums.empty()) {
return 0;
}
int longest_streak = 0;
// Pick each number one by one
for (int num : nums) {
int current_num = num;
int current_streak = 1;
// Check if the next consecutive number exists in the array
while (arrayContains(nums, current_num + 1)) {
current_num += 1;
current_streak += 1;
}
// Keep track of the maximum streak we have seen so far
longest_streak = max(longest_streak, current_streak);
}
return longest_streak;
}
};5. Complexity Analysis
While this solution is highly intuitive, its performance makes it completely unusable for larger datasets.
- Time Complexity: O(nΒ³) (Cubic Time)
- We have an outer
forloop that runs n times to inspect each number. - Inside, we have a
whileloop that, in the worst case (e.g., a perfectly consecutive array like[1, 2, 3, 4, 5]), can also run up to n times. - Finally, inside that while loop, our helper function performs a linear scan across the array to find the next number, which takes O(n) time.
- Multiplying these three nested layers together () yields a massive worst-case time complexity of O(nΒ³). If an interviewer gives you an array of just 1,000 elements, this approach could require up to a billion operations!
- We have an outer
- Space Complexity: O(1) (Constant Space)
- The only advantage of this method is that it is highly memory-efficient. We are modifying nothing and only tracking the counts using basic integer variables (
longest_streak,current_streak), requiring no extra memory layout.
- The only advantage of this method is that it is highly memory-efficient. We are modifying nothing and only tracking the counts using basic integer variables (
Article Note: Passing this brute-force solution on platforms like LeetCode will instantly result in a TLE (Time Limit Exceeded) error. It serves as the perfect stepping stone to explain why we need to optimize our search time using smarter strategies.
The Better Approach: Sorting
If the main issue with the brute force method is that elements are scattered all over the place, the most logical first optimization is to bring order to the chaos. By sorting the array, elements that belong in a consecutive sequence will be forced to sit right next to each other.
1. Intuition
Let's take our original unsorted array: [100, 4, 200, 1, 3, 2]
If we sort this array in ascending order, it becomes: [1, 2, 3, 4, 100, 200]
Now, instead of scanning the entire array over and over again to find the next number, we can just walk through the sorted array a single time (a linear scan). We check if the current element is exactly 1 greater than the previous element. If it is, we extend our streak!
2. Handling the Duplicate Trap
Before writing the code, there is a massive edge case your readers need to know about: Duplicates.
Consider an array like [1, 2, 2, 3, 4]. When we are at index 1 (2) and look at index 2 (2), the difference is 0. The streak hasn't broken, but it also hasn't grown. If our code isn't careful, seeing a duplicate might falsely reset our streak counter.
The Fix: When we encounter two identical adjacent numbers, we simply skip the element and move on without incrementing or resetting our streak.
3. The Algorithm
- Check if the array is empty. If it is, return
0. - Sort the array in ascending order.
- Initialize two variables:
longest_streak = 1andcurrent_streak = 1. - Loop through the sorted array starting from the second element (index 1):
- Case A (Consecutive): If the current element is exactly equal to
previous_element + 1, incrementcurrent_streakby 1. - Case B (Duplicate): If the current element is equal to
previous_element, do nothing and skip to the next iteration. - Case C (Broken Chain): If the chain breaks, update
longest_streak = max(longest_streak, current_streak)and resetcurrent_streak = 1.
- Case A (Consecutive): If the current element is exactly equal to
5. Return max(longest_streak, current_streak) to catch any streaks running to the very end of the array.
Dry Run
Sorted Array: [1, 2, 2, 3, 4, 100]
Initial State:
βββ longest_streak = 1
βββ current_streak = 1
π i = 1 (Value: 2 | Previous: 1)
βββ Condition: Consecutive element! (2 == 1 + 1)
βββ Action: current_streak++ β‘οΈ 2
βββ Status: Streak is growing
π i = 2 (Value: 2 | Previous: 2)
βββ Condition: Duplicate element! (2 == 2)
βββ Action: Do nothing, skip to next index
βββ Status: Streak is paused
π i = 3 (Value: 3 | Previous: 2)
βββ Condition: Consecutive element! (3 == 2 + 1)
βββ Action: current_streak++ β‘οΈ 3
βββ Status: Streak is growing
π i = 4 (Value: 4 | Previous: 3)
βββ Condition: Consecutive element! (4 == 3 + 1)
βββ Action: current_streak++ β‘οΈ 4
βββ Status: Streak is growing
π i = 5 (Value: 100 | Previous: 4)
βββ Condition: Chain is broken! (100 != 4 + 1)
βββ Action:
β βββ longest_streak = max(1, 4) β‘οΈ 4 π₯ (Updated)
β βββ current_streak resets to 1
βββ Status: Streak is dead
π Final Check: max(longest_streak, current_streak) β‘οΈ max(4, 1)
π FINAL ANSWER: 4C++ code
class Solution {
public:
int longestConsecutive(vector<int>& nums) {
// Edge case: if array is empty, return 0
if (nums.empty()) {
return 0;
}
// Sort the array first
sort(nums.begin(), nums.end());
int longest_streak = 1;
int current_streak = 1;
// Linear scan through the sorted array
for (int i = 1; i < nums.size(); i++) {
// Skip duplicates
if (nums[i] != nums[i - 1]) {
// If consecutive, increase current streak
if (nums[i] == nums[i - 1] + 1) {
current_streak++;
}
// If chain breaks, save the max and reset current streak
else {
longest_streak = max(longest_streak, current_streak);
current_streak = 1;
}
}
}
// One final check to capture the last running streak
return max(longest_streak, current_streak);
}
};6. Complexity Analysis
- Time Complexity: O(n log n)
- The most expensive operation here is sorting the array, which takes time. The subsequent linear scan only takes time. Therefore, the overall runtime is dominated by the sort step: O(n log n).
- Space Complexity: O(1)
- Since we are using C++'s
std::sort, it sorts the array in-place, meaning we don't allocate any extra tracking arrays or data structures.
- Since we are using C++'s
While sorting got us closer to the finish line, we are still tied down by that O(nlog n) sorting bottleneck. To achieve a true, lightning-fast O(n) runtime, we need a way to look up elements instantly without changing their positions. Enter: The Hash Set approach.
This is the climax of your article! This is where you show your readers how to completely shatter that bottleneck and achieve a blazing-fast, interview-ready time complexity using a Hash Set.
The Optimal Approach: The Hash Set "Chain Leader" Trick
To completely eliminate sorting, we need a way to look up if a number exists instantly. A Hash Set gives us exactly that: average time complexity for lookups.
But if we just put everything in a set and search for the next numbers blindly, we run into the same problem as the brute force methodβredundant work. We need a way to only start counting a sequence when we are standing at the absolute beginning of it.
1. Core Intuition: Finding the "Chain Leader"
Imagine a sequence of consecutive numbers scattered in your array: [1, 2, 3, 4].
If we check for a streak starting at 2, 3, or 4, we are doing useless, repetitive calculations. We only want to kick off our counting loop when we are standing at 1βthe Chain Leader.
How do we identify a Chain Leader?
A number num is the absolute start of a sequence if num - 1 does not exist in the array.
- Is
3a leader? Check3 - 1 = 2.2exists, so No. - Is
1a leader? Check1 - 1 = 0.0does not exist in our array, so Yes!
By using this simple check, we ensure that we only enter our counting while loop for the start of each sequence. Every element inside a sequence will be visited exactly once by the counting loop, keeping our runtime beautifully linear.
2. The Optimal Algorithm
- Insert all elements from the array into an
unordered_set. This automatically removes duplicates and sets up lookups. - Initialize
longest_streak = 0. - Loop through each unique
numin your set:- Check if it's a Chain Leader: Search the set for
num - 1. - If
num - 1is not found, it meansnumis a sequence starter! - Initialize
current_num = numandcurrent_streak = 1. - Open a
whileloop: as long ascurrent_num + 1exists in the set, keep incrementingcurrent_numandcurrent_streak. - Update
longest_streak = max(longest_streak, current_streak).
- Check if it's a Chain Leader: Search the set for
- Return
longest_streak.
3. Step-by-Step Dry Run
Let's trace how efficiently this runs using the array: nums = [100, 4, 200, 1, 3, 2]
- Hash Set created:
{100, 4, 200, 1, 3, 2} - Initial State:
longest_streak = 0
Set Elements: {100, 4, 200, 1, 3, 2}
π Checking num = 100
βββ Is (100 - 1 = 99) in set? π΄ NO β‘οΈ π 100 is a Chain Leader!
βββ Counting loop: Is 101 in set? π΄ NO
βββ Max updated: max(0, 1) β‘οΈ 1
π Checking num = 4
βββ Is (4 - 1 = 3) in set? π’ YES β‘οΈ Not a leader. Skip!
π Checking num = 200
βββ Is (200 - 1 = 199) in set? π΄ NO β‘οΈ π 200 is a Chain Leader!
βββ Counting loop: Is 201 in set? π΄ NO
βββ Max stays: max(1, 1) β‘οΈ 1
π Checking num = 1
βββ Is (1 - 1 = 0) in set? π΄ NO β‘οΈ π 1 is a Chain Leader!
βββ Counting loop:
β βββ Is 2 in set? π’ YES β‘οΈ Streak: 2
β βββ Is 3 in set? π’ YES β‘οΈ Streak: 3
β βββ Is 4 in set? π’ YES β‘οΈ Streak: 4
β βββ Is 5 in set? π΄ NO β‘οΈ Chain ends.
βββ Max updated: max(1, 4) β‘οΈ 4 π₯
π Checking num = 3
βββ Is (3 - 1 = 2) in set? π’ YES β‘οΈ Not a leader. Skip!
π Checking num = 2
βββ Is (2 - 1 = 1) in set? π’ YES β‘οΈ Not a leader. Skip!
π FINAL ANSWER: 4c++ code
class Solution {
public:
int longestConsecutive(vector<int>& nums) {
// Create a Hash Set and dump all elements into it
unordered_set<int> num_set(nums.begin(), nums.end());
int longest_streak = 0;
// Traverse the elements of the set
for (int num : num_set) {
// Check if 'num' is the absolute start of a sequence
if (num_set.find(num - 1) == num_set.end()) {
int current_num = num;
int current_streak = 1;
// Push forward to find the rest of the chain
while (num_set.find(current_num + 1) != num_set.end()) {
current_num += 1;
current_streak += 1;
}
// Track the maximum continuous chain found
longest_streak = max(longest_streak, current_streak);
}
}
return longest_streak;
}
};Complexity Analysis
- Time Complexity: (Linear Time)
- Wait, isn't there a while loop inside a for loop? Yes, it looks like O(n^2) on the surface, but it's not! The
whileloop only triggers if a number is a sequence leader. An element that belongs to a chain is only looked up and iterated through exactly once during the entire runtime inside thatwhileloop. This means each number is touched at most twice overall, yielding a true linear time complexity of .
- Wait, isn't there a while loop inside a for loop? Yes, it looks like O(n^2) on the surface, but it's not! The
- Space Complexity: O(n)(Linear Space)
- We traded a little bit of memory to gain that massive speed boost. We allocate an
unordered_setthat stores up to n elements from the original array to achieve fast O(1) lookups.
- We traded a little bit of memory to gain that massive speed boost. We allocate an
Video Reference:
https://youtu.be/oO5uLE7EUlM?si=SyxkvMC3Zw6wqw_4
Problem try out:
