Skip to content

128. Longest Consecutive Sequence (Medium)

Problem

Given an unsorted array of integers nums, return the length of the longest consecutive-integer sequence. The algorithm must run in O(n)O(n) time.

Example

  • nums = [100, 4, 200, 1, 3, 2]4 (the sequence [1, 2, 3, 4])
  • nums = [0, 3, 7, 2, 5, 8, 4, 6, 0, 1]9

LeetCode 128 · Link · Medium

Try it yourself

idle

Starter code: this editor begins with intentional TODOs. Fill the function, run the embedded tests, then compare your solution with the worked approaches below.

Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Approach 1: Brute force, for each number, walk forward

For each number x, linearly scan the array for x+1, x+2, until a miss.

def longest_consecutive(nums: list[int]) -> int:
best = 0 # L1: O(1)
for x in nums: # L2: outer loop, n iterations
cur = x # L3: O(1)
length = 1 # L4: O(1)
while cur + 1 in nums: # L5: O(n) membership on a list, per call
cur += 1 # L6: O(1)
length += 1 # L7: O(1)
best = max(best, length) # L8: O(1)
return best

Where the time goes, line by line

Variables: n = len(nums).

LinePer-call costTimes executedContribution
L2 (outer loop)O(1)O(1)nO(n)O(n)
L5 (list membership)O(n)O(n)up to n per outer iterationO(n3)O(n³) ← dominates
L3, L4, L6, L7, L8O(1)O(1)proportional to run lengthsO(n2)O(n²) at most

Each in nums on a list costs O(n)O(n), called up to n times per outer loop iteration.

Complexity

  • Time: O(n3)O(n³) in the worst case, driven by L5 (O(n)O(n) membership called up to n times per outer).
  • Space: O(1)O(1) extra.

Clearly doesn’t meet the O(n)O(n) requirement, useful to see what the naive instinct would cost.

final class Solution {
func longestConsecutive(_ nums: [Int]) -> Int {
let values = Set(nums); var best = 0
for value in nums { var current = value, length = 0; while values.contains(current) { length += 1; current += 1 }; best = max(best, length) }
return best
}
}

Approach 2: Sort, then count runs

After sorting, runs of consecutive integers are adjacent. Walk the sorted array.

def longest_consecutive(nums: list[int]) -> int:
if not nums: # L1: O(1) guard
return 0
nums_sorted = sorted(set(nums)) # L2: O(n log n) sort after O(n) dedup
best = cur = 1 # L3: O(1)
for i in range(1, len(nums_sorted)): # L4: loop, n iterations
if nums_sorted[i] == nums_sorted[i - 1] + 1: # L5: O(1) comparison
cur += 1 # L6: O(1)
best = max(best, cur) # L7: O(1)
else:
cur = 1 # L8: O(1) reset
return best

Where the time goes, line by line

Variables: n = len(nums).

LinePer-call costTimes executedContribution
L2 (sort)O(nlogn)O(n log n)1O(nlogn)O(n log n) ← dominates
L4-L8 (linear scan)O(1)O(1)nO(n)O(n)

The sort owns the cost; the rest is a single linear pass.

Complexity

  • Time: O(nlogn)O(n log n), dominated by L2 (the sort).
  • Space: O(n)O(n) for the sorted set.

Correct and simple, but violates the explicit O(n)O(n) constraint in the prompt.

Try this approach:

idle
Click Run Python to execute. First run downloads Python (~10 MB, cached after that).
final class Solution {
func longestConsecutive(_ nums: [Int]) -> Int {
guard !nums.isEmpty else { return 0 }
let sorted = nums.sorted(); var best = 1, current = 1
for index in 1..<sorted.count { if sorted[index] == sorted[index - 1] { continue }; if sorted[index] == sorted[index - 1] + 1 { current += 1 } else { current = 1 }; best = max(best, current) }
return best
}
}

Approach 3: Hash set + run-start detection (optimal)

Put everything in a set. For each number, only start counting a run if x - 1 is not in the set (so x is a run start). Then walk upward as long as the next integer is present.

Each element is touched by a walking pointer at most once across the whole algorithm, giving total O(n)O(n).

def longest_consecutive(nums: list[int]) -> int:
num_set = set(nums) # L1: O(n) set construction
best = 0 # L2: O(1)
for x in num_set: # L3: outer loop, n iterations
if x - 1 in num_set: # L4: O(1) set lookup, skip non-starts
continue
cur = x # L5: O(1)
length = 1 # L6: O(1)
while cur + 1 in num_set: # L7: O(1) set lookup per call
cur += 1 # L8: O(1)
length += 1 # L9: O(1)
best = max(best, length) # L10: O(1)
return best

Where the time goes, line by line

Variables: n = len(nums).

LinePer-call costTimes executedContribution
L1 (set construction)O(n)O(n)1O(n)O(n)
L3 (outer loop)O(1)O(1)nO(n)O(n)
L4 (skip non-starts)O(1)O(1)nO(n)O(n)
L7 (inner while, set lookup)O(1)O(1)n total across all startsO(n)O(n) ← key insight
L10 (max)O(1)O(1)per startO(n)O(n)

L4’s guard ensures each element is walked from its run-start exactly once. Even though L7 is inside a while loop, the total number of iterations across all outer iterations is at most n (each element visited once as a “next” step).

Complexity

  • Time: O(n)O(n), driven by L1 (set build) plus the amortized-O(n)O(n) total inner-while work at L7.
  • Space: O(n)O(n) for the set.

Try this approach:

idle
Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Alternative: Union-Find

A second O(n)O(n) approach unions every x with x - 1 and x + 1 in a disjoint-set structure and returns the largest component size. Same asymptotics, more code, worth knowing for the pattern.

final class Solution {
func longestConsecutive(_ nums: [Int]) -> Int {
let values = Set(nums); var best = 0
for value in values where !values.contains(value - 1) { var current = value, length = 1; while values.contains(current + 1) { current += 1; length += 1 }; best = max(best, length) }
return best
}
}

Summary

ApproachTimeSpace
For-each + linear searchO(n3)O(n³)O(1)O(1)
Sort + countO(nlogn)O(n log n)O(n)O(n)
Hash set + run startO(n)O(n)O(n)O(n)

The run-start trick is exact-fit to the problem’s constraint. Recognize “I need O(n)O(n) but I also need order” as the signal to reach for a hash set with an invariant.

Test cases

# Quick smoke tests, paste into a REPL or save as test_longest_consecutive.py and run.
# Uses the canonical implementation (Approach 3: hash set + run-start detection).
def longest_consecutive(nums: list[int]) -> int:
num_set = set(nums)
best = 0
for x in num_set:
if x - 1 in num_set:
continue
cur = x
length = 1
while cur + 1 in num_set:
cur += 1
length += 1
best = max(best, length)
return best
def _run_tests():
assert longest_consecutive([100, 4, 200, 1, 3, 2]) == 4
assert longest_consecutive([0, 3, 7, 2, 5, 8, 4, 6, 0, 1]) == 9
assert longest_consecutive([]) == 0
assert longest_consecutive([1]) == 1
assert longest_consecutive([1, 2, 3, 4, 5]) == 5
assert longest_consecutive([5, 4, 3, 2, 1]) == 5
print("all tests pass")
if __name__ == "__main__":
_run_tests()
  • Arrays, input
  • Hash Tables, set membership for O(1)O(1) lookups; run-start detection
  • Hash Map Counting, the lookup table pattern for complements, frequencies, and seen items.
  • Array Scans, the linear pass habit of carrying just enough state while reading each item once.