Solving tips
- Recognize the O(n) bar rules out sorting: put all values in a set for O(1) membership, then grow runs by probing v+1, v+2, ....
- Key insight: only start a walk from a run's left endpoint (a value v where v-1 is NOT in the set), so each element is visited O(1) times overall.
- Target O(n) time, O(n) space; without the start-check the walk degrades to O(n^2) on an already-consecutive array β exactly the tested case.
- Pitfall: do membership tests against the set (not the list), and return 0 for the empty array.
Problem
Given an unsorted array of integers nums, return the length of the longest run of consecutive integer values that all appear somewhere in the array. The values only need to exist in the array β their positions donβt matter, and duplicates count once.
The required time complexity is O(n) β which rules out sorting as the final answer, even though sorting is a fine warm-up.
Examples
Example 1: nums = [100, 4, 200, 1, 3, 2] β 4
The values 1, 2, 3, 4 all appear, forming a consecutive run of length 4; 100 and 200 are isolated.
Example 2: nums = [0, 3, 7, 2, 5, 8, 4, 6, 0, 1] β 9
Every value 0 through 8 appears (the duplicate 0 counts once), a run of length 9.
Example 3: nums = [] β 0
No elements, no run.
Constraints
0 <= nums.length <= 10^5
-10^9 <= nums[i] <= 10^9
10^5 elements with values up to 10^9: no counting array over the value range, and quadratic scans (~10^10) are out. The O(n) bar is the point of the problem.
Think about it first
Hint 1
If you could ask "is value v in the array?" in O(1), how would you grow a run starting from some value?
Hint 2
Walking upward (v, v+1, v+2, ...) from *every* element re-walks the same run from every one of its members β that's O(nΒ²) in the worst case. Which elements are worth starting from?
Hint 3
Put everything in a set. Only start counting from values v where `v - 1` is **not** in the set β the run's left endpoint. Every element is then visited O(1) times total across all walks.
TL;DR
Hash set + start-of-run detection β O(n) time, O(n) space.
Approach 1 β Brute force (walk up from every element)
The naive intuition: for each value, keep asking βis value+1 also here?β by scanning the array, extending as far as possible.
from typing import List
class Solution:
def longestConsecutive(self, nums: List[int]) -> int:
best = 0
for v in nums:
length = 1
while v + length in nums: # O(n) list scan per probe
length += 1
best = max(best, length)
return best
Complexity: each in nums on a list is O(n), each walk is up to O(n) probes, over n starts: O(nΒ³) worst case, O(1) space.
At n = 10^5 thatβs ~10^15 operations β hopeless.
Approach 2 β Sort, then scan runs
The insight: after sorting, a consecutive run of values sits contiguously, so one linear scan finds the longest run β just skip duplicates rather than resetting on them.
from typing import List
class Solution:
def longestConsecutive(self, nums: List[int]) -> int:
if not nums:
return 0
nums.sort()
best = 1
current = 1
for i in range(1, len(nums)):
if nums[i] == nums[i - 1]:
continue # duplicate: run unchanged
if nums[i] == nums[i - 1] + 1:
current += 1
else:
current = 1
best = max(best, current)
return best
Walkthrough on Example 1, nums = [100, 4, 200, 1, 3, 2]:
- Sorted:
[1, 2, 3, 4, 100, 200].
- 2 = 1+1 β current 2; 3 = 2+1 β current 3; 4 = 3+1 β current 4 (best 4).
- 100 β 4+1 β reset to 1; 200 β 100+1 β reset to 1.
- Answer 4. β
Complexity: O(n log n) time for the sort, O(1) extra space (in-place). Correct and simple β it just misses the problemβs stated O(n) bar.
Approach 3 β Hash set with start-of-run detection
The insight: a set gives O(1) membership, but walking upward from every element still re-traverses each run once per member (O(nΒ²) worst case, e.g. [1..n]). The fix: only launch a walk from a runβs left endpoint β a value v with v - 1 absent from the set. Each run is then walked exactly once, and every element is touched O(1) times overall.
from typing import List
class Solution:
def longestConsecutive(self, nums: List[int]) -> int:
values = set(nums)
best = 0
for v in values:
if v - 1 in values:
continue # not a run start
length = 1
while v + length in values:
length += 1
best = max(best, length)
return best
Walkthrough on Example 1, values = {1, 2, 3, 4, 100, 200}:
- v = 1: 0 not in set β run start. Probe 2 β, 3 β, 4 β, 5 β β length 4, best = 4.
- v = 2, 3, 4: each has its predecessor in the set β skipped in O(1).
- v = 100: 99 absent β start; 101 absent β length 1.
- v = 200: 199 absent β start; 201 absent β length 1.
- Answer 4. β
Complexity: O(n) time β the start-check is O(1) per element, and all upward probes combined touch each set member once (each element belongs to exactly one run). O(n) space for the set.
Common pitfalls
- Skipping the
v - 1 not in values guard β the code still returns the right answer but degrades to O(nΒ²) on an already-consecutive array, which is precisely the case the problem tests.
- Doing membership tests against the list instead of a set β same silent complexity blowup (O(n) per probe).
- Forgetting duplicates: in the sorting approach,
nums[i] == nums[i-1] must neither extend nor reset the run; the set approach dedupes for free.
- Not handling the empty array (return 0, not a crash on
max of nothing).
Pattern takeaway
A hash set turns βdoes value x exist?β into O(1), but the patternβs second half matters just as much: pick canonical starting points (here, run left-endpoints) so that aggregate work is charged once per element instead of once per element-pair. βO(1) membership + walk only from boundariesβ is the standard route to linear time on value-adjacency problems.