Longest Consecutive Sequence in Java DSA: Brute Force, Sorting, and the HashSet Trick
-
Last Updated: August 10, 2026
-
By: javahandson
-
Series
Learn Java in a easy way
Solve Longest Consecutive Sequence in Java DSA three ways — brute force, sorting, and the O(n) HashSet trick — with full step-by-step dry runs for beginners.
The Longest Consecutive Sequence in Java is a problem that looks harder than it really is. You get an array of numbers that sit in no special order. Your job is to find the length of the longest run of numbers that follow each other, like 1, 2, 3, 4.
The numbers do not have to be next to each other in the array. They just need to exist somewhere in it. So an array like 100, 4, 200, 1, 3, 2 hides the run 1, 2, 3, 4 scattered across it, and the answer is 4.
There is one twist that makes this fun. The best solution runs in linear time, even though sorting feels like the obvious path. That gap between the easy idea and the fast idea is what interviewers love to test.
We build the answer in three steps, like always. Brute force checks every number as a possible start. Sorting lines the numbers up first. The HashSet trick skips sorting and still finishes in one pass over the data.
Every approach gets a full, step-by-step dry run on the same array. Nothing is skipped, so you can watch each number get checked and see exactly why the count grows or resets.
Let us pin down the rules before we write any code.
For our array the answer is 4, because the numbers 1, 2, 3, 4 all appear. The stray 100 and 200 sit alone, and the extra 4 is just a repeat that changes nothing.
A consecutive run is built by adding one each time. If you have the number 1, you look for 2. If 2 is there, you look for 3, and so on until the next number is missing.
A HashSet stores numbers and lets you ask “is this number here?” almost instantly. That single power is what turns a slow search into a fast one.
Take each number and pretend it starts a run. From that number, look for the next one, then the one after that, and keep going. Count how far you get. Do this for every number and keep the longest count you saw.
best = 0
for each num in nums:
current = num
length = 1
while (current + 1) exists in nums: // scan the array
current = current + 1
length = length + 1
best = max(best, length)
return bestThe plan is simple to picture. We treat every number as if it might be the first number of a run, then measure how long that run stretches. Whichever start gives the longest run wins.
Setting up the tracker. Before the loop, best starts at 0. This one variable remembers the longest run we have seen across all starting points. It only ever moves up, never down.
Starting a fresh run. For each number in the array, two things get set:
Growing the run. The while loop asks one question over and over: is current + 1 somewhere in the array? Each time the answer is yes, the run can reach one number higher.
Ending and saving. The moment current + 1 is missing from the array, the run cannot grow, so the while loop stops. Then best = max(best, length) keeps whichever is bigger, the old best or this run. After every number has taken its turn as a start, best holds the answer.
public class LongestConsecutiveBrute {
public static int longestConsecutive(int[] nums) {
int best = 0;
for (int num : nums) {
int current = num;
int length = 1;
while (contains(nums, current + 1)) {
current = current + 1;
length = length + 1;
}
best = Math.max(best, length);
}
return best;
}
private static boolean contains(int[] nums, int target) {
for (int n : nums) {
if (n == target) {
return true;
}
}
return false;
}
public static void main(String[] args) {
int[] nums = {100, 4, 200, 1, 3, 2, 4};
System.out.println(longestConsecutive(nums)); // 4
}
}This is the same plan in Java. The helper method contains does the searching for us, so the main logic stays clean.
Let us trace the array nums = 100, 4, 200, 1, 3, 2, 4. We treat each number as a start and see how far its run goes. Every time we ask for a next number, contains scans the array to answer.
| Start number | Run walked | Length | best after this |
|---|---|---|---|
| 100 | 100 (101 missing) | 1 | 1 |
| 4 | 4 (5 missing) | 1 | 1 |
| 200 | 200 (201 missing) | 1 | 1 |
| 1 | 1 → 2 → 3 → 4 (5 missing) | 4 | 4 |
| 3 | 3 → 4 (5 missing) | 2 | 4 |
| 2 | 2 → 3 → 4 (5 missing) | 3 | 4 |
| 4 | 4 (5 missing) | 1 | 4 |
After checking all seven numbers, the longest run we saw was 4, from the start number 1. So the method returns 4.
Let us walk every starting number one at a time. For each, we watch the while loop ask for the next number, and we see why the run either grows or dies right away.
Start 100. We set current to 100 and length to 1. The while loop now asks if 101 is in the array. contains scans all seven numbers, 100, 4, 200, 1, 3, 2, 4, and finds no 101. So the loop never runs even once. The run stays at length 1. Since best was 0, best = max(0, 1) makes best become 1.
Start 4 (the first 4). Next we set current to 4 and length to 1. The loop asks if 5 is in the array. contains scans everything and finds no 5. The loop stops immediately, so this run is length 1. best = max(1, 1) leaves best at 1, unchanged.
Start 200. Now current is 200 and length is 1. The loop asks for 201. Again contains finds nothing, so the run is length 1. best stays at 1. Notice a pattern forming: every lone number with no neighbour above it dies at length 1.
Start 1, the long run. Here we set current to 1 and length to 1, and this time the loop keeps going. Let us trace each pass:
The run ended at length 4. best = max(1, 4) jumps best up to 4. This is the answer, though the loop does not know that yet and keeps checking the rest.
Start 3. We set current to 3 and length to 1. The loop asks for 4, finds it, so current becomes 4 and length becomes 2. Then it asks for 5, finds nothing, and stops. This run is length 2. best = max(4, 2) keeps best at 4. Notice we just re-walked 3 to 4, ground the start at 1 already covered.
Start 2. We set current to 2 and length to 1. The loop finds 3, so current becomes 3 and length becomes 2. It then finds 4, so current becomes 4 and length becomes 3. Finally it asks for 5, finds nothing, and stops. This run is length 3, but best stays at 4. Once again we re-walked numbers the start at 1 already handled.
Start 4 (the second 4). The duplicate 4 gets its own turn. current is 4 and length is 1. The loop asks for 5, finds nothing, and stops. Length 1, and best stays at 4.
Two starting numbers, 3 and 2, walked over the exact same 3 to 4 path that the start at 1 already traced. That repeated walking is wasted work, and it is precisely what the HashSet approach removes.
💡 Interview Insight Interviewers often ask “why is brute force slow here?” Point out that many numbers re-walk the same run. Starting at 1, 2, and 3 all end at 4, so the same steps get repeated again and again.
The brute force is easy to picture, but that repeated scanning kills its speed. Sorting is our next step, and it removes the need to scan for every next number.
Sort the numbers first. Once they are in order, a consecutive run sits right next to each other. Walk the sorted array and count how long each run of back-to-back numbers gets. Duplicates are skipped so they do not break the count.
if nums is empty:
return 0
sort(nums)
best = 1
current = 1
for i from 1 to length(nums) - 1:
if nums[i] == nums[i-1]:
skip // duplicate, ignore
else if nums[i] == nums[i-1] + 1:
current = current + 1
best = max(best, current)
else:
current = 1 // run broke, reset
return bestOnce the numbers are sorted, a consecutive run turns into a stretch of neighbours that each go up by exactly one. So instead of searching, we just walk left to right and watch the gaps between neighbours.
The empty guard. First we handle the empty array. With no numbers there is no run at all, so we return 0 straight away. This also stops us from reading nums[i-1] when there is nothing to read.
The two trackers. After sorting, we set up two variables, both starting at 1:
Walking and comparing. The loop starts at index 1 so we can always compare nums[i] with the number just before it, nums[i-1]. Each step falls into one of three cases:
The result. Because best only ever climbs and never falls, a reset in the else branch never loses the longest run we already found. After the walk, best holds the answer.
import java.util.*;
public class LongestConsecutiveSort {
public static int longestConsecutive(int[] nums) {
if (nums.length == 0) {
return 0;
}
Arrays.sort(nums);
int best = 1;
int current = 1;
for (int i = 1; i < nums.length; i++) {
if (nums[i] == nums[i - 1]) {
continue;
} else if (nums[i] == nums[i - 1] + 1) {
current = current + 1;
best = Math.max(best, current);
} else {
current = 1;
}
}
return best;
}
public static void main(String[] args) {
int[] nums = {100, 4, 200, 1, 3, 2, 4};
System.out.println(longestConsecutive(nums)); // 4
}
}The Java version follows the same walk. Arrays.sort does the ordering, and continue handles the duplicate case neatly.
We start with nums = 100, 4, 200, 1, 3, 2, 4. After Arrays.sort the array becomes 1, 2, 3, 4, 4, 100, 200. Now we walk from index 1 and compare each number with the one before it.
The legend: current is the length of the run we are on right now, and best is the longest run seen so far.
| i | nums[i] | nums[i-1] | Case | current | best |
|---|---|---|---|---|---|
| 1 | 2 | 1 | one bigger → extend | 2 | 2 |
| 2 | 3 | 2 | one bigger → extend | 3 | 3 |
| 3 | 4 | 3 | one bigger → extend | 4 | 4 |
| 4 | 4 | 4 | duplicate → skip | 4 | 4 |
| 5 | 100 | 4 | big jump → reset | 1 | 4 |
| 6 | 200 | 100 | big jump → reset | 1 | 4 |
After the walk, best holds 4, which is the length of the run 1, 2, 3, 4. So the method returns 4.
We walk the sorted array 1, 2, 3, 4, 4, 100, 200 from index 1. At every step we compare nums[i] with nums[i-1] and decide which of the three cases we are in. Let us take each index in turn.
Index 1, number 2. We compare 2 with the number just before it, which is 1. The gap is exactly one, so we are in the “one bigger” case. The run is alive, so current climbs from 1 to 2. Then best = max(1, 2) lifts best to 2 as well. Our run so far is 1, 2.
Index 2, number 3. Now we compare 3 with 2. Once more the gap is exactly one, so the run keeps going. current rises from 2 to 3, and best = max(2, 3) lifts best to 3. The run is now 1, 2, 3.
Index 3, number 4. Here we compare 4 with 3. The step is one again, so the run extends. current reaches 4, and best = max(3, 4) lifts best to 4. The run is now 1, 2, 3, 4, and this is the peak.
Index 4, the duplicate 4. This time we compare 4 with the previous 4. They are equal, so we hit the duplicate case and skip. Skipping is important here:
Index 5, number 100. We compare 100 with 4. The gap is 96, far more than one, so the run is broken. We fall into the else case and reset current back to 1, starting a fresh run at 100. best stays safely at 4, because the reset only touches current, never best.
Index 6, number 200. Finally we compare 200 with 100. The gap is 100, so again the run breaks. current resets to 1 once more. There is no real run out here among the big numbers, so best holds its 4 all the way to the end.
Sorting did the heavy lifting by placing 1, 2, 3, 4 right next to each other, which made the run trivial to count. The price we paid was the sort itself, and that is the cost the HashSet approach avoids.
💡 Interview Insight A common follow-up is “why not just return the longest run without the duplicate check?” Without skipping duplicates, a repeated number looks like a break and wrongly resets your count. The skip keeps equal neighbours from spoiling the run.
Sorting is a big jump over brute force. Still, that log n factor is avoidable. The HashSet approach does the same job in linear time by walking only true starts.
Put every number in a HashSet. Then, for each number, ask a smart question first: is the number before it in the set? If yes, this number sits in the middle of some run, so skip it. If no, this number is a true start, so walk forward counting until the next number is missing. Because we only ever walk from real starts, each number is touched a small, fixed number of times.
put all numbers into a set
best = 0
for each num in set:
if (num - 1) is NOT in set: // num is a start
current = num
length = 1
while (current + 1) is in set:
current = current + 1
length = length + 1
best = max(best, length)
return bestThe whole trick sits in one small question we ask before counting. Instead of walking a run from every number, we only walk from the number that truly begins a run. That single filter is what removes all the repeated work.
Building the set. First we drop every number into a set. The set does two jobs for us. It removes duplicates, so a repeated number cannot cause extra walks. It also lets us check “is this number here?” almost instantly, which we lean on heavily below.
The start check. For each number, we ask: is num – 1 in the set?
Walking forward from a start. Once we know num is a start, we set current to num and length to 1. Then the while loop steps forward:
Why this stays fast. Because we only ever walk from a real start, each run is counted exactly once, no matter how many of its numbers we loop over. After all numbers are checked, best = max(best, length) leaves the answer in best.
import java.util.*;
public class LongestConsecutiveSet {
public static int longestConsecutive(int[] nums) {
Set<Integer> set = new HashSet<>();
for (int n : nums) {
set.add(n);
}
int best = 0;
for (int num : set) {
if (!set.contains(num - 1)) {
int current = num;
int length = 1;
while (set.contains(current + 1)) {
current = current + 1;
length = length + 1;
}
best = Math.max(best, length);
}
}
return best;
}
public static void main(String[] args) {
int[] nums = {100, 4, 200, 1, 3, 2, 4};
System.out.println(longestConsecutive(nums)); // 4
}
}The code builds the set, then loops over it. The single if on line 12 is the whole idea, and set.contains gives the fast lookups.
We start with nums = 100, 4, 200, 1, 3, 2, 4. First we build the set, which drops the duplicate 4. The set holds 1, 2, 3, 4, 100, 200.
Now we loop over the set. For small integers Java visits them in rising order, so we go 1, 2, 3, 4, 100, 200. For each one, we first check if the number below it is present.
| num | Is (num-1) in set? | Start? | Walk forward | length | best |
|---|---|---|---|---|---|
| 1 | 0 → no | Yes | 1 → 2 → 3 → 4 (5 missing) | 4 | 4 |
| 2 | 1 → yes | No | skip | – | 4 |
| 3 | 2 → yes | No | skip | – | 4 |
| 4 | 3 → yes | No | skip | – | 4 |
| 100 | 99 → no | Yes | 100 (101 missing) | 1 | 4 |
| 200 | 199 → no | Yes | 200 (201 missing) | 1 | 4 |
Only the true starts 1, 100, and 200 triggered a walk. The start at 1 gave the run of length 4, and the method returns 4.
We loop over the set 1, 2, 3, 4, 100, 200. For each number, the first thing we do is the start check: is the number one smaller in the set? That check decides whether we walk or skip. Let us go through each number.
num = 1, a true start. We ask if 0 is in the set. It is not, so 1 has nothing before it, which makes it a real start. We set current to 1 and length to 1, then walk forward:
This run finished at length 4, so best = max(0, 4) sets best to 4. The entire longest run got counted in this one walk.
num = 2, skipped. We ask if 1 is in the set. It is, so 2 is not a start, it sits in the middle of a run. We skip it without walking at all. This matters: the run through 2 was already counted when we started at 1, so walking again would be pure waste.
num = 3, skipped. We ask if 2 is in the set. It is, so 3 is not a start either. We skip it. Again, the run covering 3 was already handled by the start at 1.
num = 4, skipped. We ask if 3 is in the set. It is, so 4 is not a start. We skip it too. So the three numbers 2, 3, and 4 each cost only a single set lookup, not a full walk. That is the exact repeated work that made brute force slow, now avoided.
num = 100, a lone start. We ask if 99 is in the set. It is not, so 100 is a start. We set current to 100 and length to 1, then check for 101. It is missing, so the walk stops at once. This run is length 1, and best = max(4, 1) keeps best at 4.
num = 200, another lone start. We ask if 199 is in the set. It is not, so 200 is a start as well. We check for 201, find nothing, and stop. This run is also length 1, so best stays at 4 to the very end.
The heart of this approach is that start check. It guarantees we only walk a run from its first number, so no number is ever part of two walks. Add up all the walk steps and they total the number of elements, which is why the whole method runs in linear time.
💡 Interview Insight The classic question is “how is this O(n) when there is a loop inside a loop?” Explain that the inner while only runs from a start, and each number is visited by at most one walk. So across the whole run the inner steps add up to n, not n squared.
| Approach | How it decides | Wasted work | Speed feel |
|---|---|---|---|
| Brute force | Walk a run from every number | Re-walks the same run many times | Slowest of the three |
| Sort | Count neighbours in sorted order | Pays for a full sort | Middle |
| HashSet | Walk only from true starts | None, each number touched once | Fastest |
All three return the same answer. They just pay different prices to get there.
| Approach | Time | Space | Note |
|---|---|---|---|
| Brute force | O(n squared) | O(1) | Simple, but re-walks runs |
| Sort the array | O(n log n) | O(1) | Neat, but pays for sorting |
| HashSet | O(n) | O(n) | Fastest, the expected answer |
The HashSet spends extra memory to store the set, but it buys linear time in return. That trade is almost always worth it for this problem.
In an interview, mention brute force to show you understand the naive path. Then bring up sorting as a clear improvement. Land on the HashSet for the linear-time answer, and be ready to explain why the inner loop does not make it quadratic.
💡 Interview Insight If pushed on the space cost, admit the set uses O(n) memory. Then point out that this is the price for skipping the sort and reaching O(n) time, which is the whole goal of the problem.
A few small traps catch beginners on this problem. Keep them in mind.
Run an empty array and a single-number array through your code before you call it done. These edge cases catch more bugs than any normal input will.
A: The inner while loop only runs when a number is a true start (its predecessor is absent). Each number is visited by at most one forward walk, so the inner steps add up to n across the whole array, not n squared.
A: No. Two copies of the same number count as one. The HashSet drops duplicates automatically, and the sort approach skips repeated neighbours so they do not reset the count.
A: Yes. Consecutive just means each number is one more than the last, so negatives work the same way. A run like -2, -1, 0, 1 is perfectly valid.
A: An empty array returns 0, since there are no numbers to form a run. A single-number array returns 1, because one number is a run of length one.
The Longest Consecutive Sequence in Java teaches a habit worth keeping: a smart check can beat a heavy tool. Sorting feels natural, but a set plus one clever question does the job faster.
Our seven-number array showed the payoff clearly. Brute force wasted effort re-walking the same run. Sorting lined things up but paid for the ordering. The HashSet walked each run exactly once and finished in linear time.
So take the pattern, not just the answer. When you find yourself re-doing the same work, look for a way to start only from the real beginning. That single idea shows up again and again in array problems.
Master this one, and you will spot the same trick in many harder questions further down the list.