Array · 18 of 36
Find longest consecutive subsequence
Find the longest run of numbers that follow each other, like 1, 2, 3, 4, hidden anywhere in a messy list.
- Simple way
- time O(n log n)
room O(1) - Clever way
- time O(n)
room O(n)
The problem, simply
You get an unsorted array of whole numbers. Find the longest set of values that make a run of numbers that follow each other, like 5, 6, 7, 8. The values can be anywhere in the array, in any order. Give back the length of that run.
Repeated values count once. An empty array has answer 0.
A story to remember it
Picture it
The numbers 1, 2, 3 and 4 are spread out in the array, but together they make a run of 4. 100 and 200 are runs of 1.
- answer
Examples
A normal case
- In
[100, 4, 200, 1, 3, 2]- Out
4
The run is 1, 2, 3, 4.
A long hidden run
- In
[0, 3, 7, 2, 5, 8, 4, 6, 0, 1]- Out
9
0 to 8 are all there (0 twice). The repeat does not make the run longer.
Negatives
- In
[-1, -2, 0, 5]- Out
3
-2, -1, 0 is a run of three.
Empty
- In
[]- Out
0
No numbers, no run.
Way 1: the simple way
Sort the array. Now runs sit next to each other. Walk along and count how long each run is.
- Sort.
- For each
i > 0: ifarr[i]equalsarr[i-1], it is a repeat, skip it. Ifarr[i] == arr[i-1] + 1, the run grows. Otherwise a new run starts at length 1. - Keep the longest run.
After sorting [100, 4, 200, 1, 3, 2] we get 1, 2, 3, 4, 100, 200. The run 1 to 4 sits together.
- answer
class Solution {
public int longestConsecutive(int[] arr) {
if (arr.length == 0) return 0;
int[] a = arr;
Arrays.sort(a); // sorts the input in place
int best = 1, run = 1;
for (int i = 1; i < a.length; i++) {
if (a[i] == a[i - 1]) continue; // a repeat does not change the run
if (a[i] == a[i - 1] + 1) run++; // the run keeps going
else run = 1; // a gap: a new run starts
best = Math.max(best, run);
}
return best;
}
}How long it takes: sorting costs O(n log n), the walk O(n). Extra room: O(1) besides the sort itself, because we sort in place. Copy the array first if the caller needs it unchanged. The very first idea, "for each number, search the whole array for the next number", is much slower at O(n^3).
Way 2: the clever way
Put every number in a bag where "is x here?" takes one step (a hash set). Then only count up from numbers that start a run.
- Add all numbers to the set.
- For each number
xin the set: ifx - 1is also in the set, skipx. It is in the middle of a run. - Otherwise
xstarts a run. Countx + 1,x + 2, ... while they are in the set. - Keep the longest count.
The skip in step 2 is what makes it fast. Without it, every number in a run of length L would count the rest of the run again, and that costs O(n^2).
Only start counting at the bottom of a run
01 / 14
Pour every number into a bag where we can check "is x here?" in one step. Repeats would collapse into one.
- best
- 0
- looking now
- checking
- done
- answer
- path
- just added
- ignored
class Solution {
public int longestConsecutive(int[] arr) {
Set<Integer> bag = new HashSet<>();
for (int x : arr) bag.add(x);
int best = 0;
for (int x : bag) { // walk the set, so repeats are seen once
if (bag.contains(x - 1)) continue; // not the front of a run
int run = 1;
while (bag.contains(x + run)) run++; // count up from the front
best = Math.max(best, run);
}
return best;
}
}How long it takes: each number is checked once as a possible start, and each number is counted inside at most one run, so about 2n lookups, O(n) on average. Extra room: the set, O(n).
Watch out for
- Loop over the set, not the array. With many copies of the front number, looping over the array repeats the same count again and again.
x + 1overflows whenxisInteger.MAX_VALUE. It wraps to the smallestint, which is only a problem if that value is also in the array. Uselongmath to be fully safe.- Repeats must not grow the run. The sort version needs the
a[i] == a[i-1]skip for this.
What interviewers ask next
- Return the run itself? Remember the front
xand the length of the best run, then outputx, x+1, .... - Can union find solve it? Yes. Join
xwithx + 1whenever both exist, and track the size of each group. The biggest group is the answer, in nearlyO(n). - What if numbers arrive one at a time and you must answer after each? Keep a map from each run's ends to its length. A new
xjoins the run ending atx - 1and the run starting atx + 1, updating only the two outer ends. - Why not sort? Sorting is
O(n log n). Interviewers asking this question usually want theO(n)set idea.
Remember this
Grown-up words: the bag is a hash set with O(1) average lookups, a run is a consecutive sequence, and the "front of the line" check is what makes the total work linear (amortized O(n)).