Problem overview
The function longestConsecutive receives an unsorted slice of integers, nums, and returns the length of the longest run of values that follow each other by exactly 1, such as 1, 2, 3, 4. The values don't have to be next to each other in the slice.
For [100, 4, 200, 1, 3, 2] the longest run is 1, 2, 3, 4, so the answer is 4. For [0, 2, 0, 1, 2, 3, 1] the answer is also 4, from 0, 1, 2, 3. Each value counts once, even when it appears several times.
The lesson asks for O(n log n) time and O(1) extra space. A hash set would give O(n) time but uses O(n) space.
Approach: sort, then measure blocks
After sorting, every run of consecutive values occupies one block of the slice, and the blocks follow one another. Inside a block, each element is either equal to the last value of the run or exactly one larger. A gap of 2 or more starts a new block.
The scan therefore walks through the sorted slice once. At the start of each block, it remembers the first value, nums[i], and extends the run while the next element is at most currentValue + 1. Duplicates are consumed without growing the run. The length of the block's run is the last value minus the first value, plus 1, because the values from a to b contain b - a + 1 numbers.
On the sorted second example, [0, 0, 1, 1, 2, 2, 3], the whole slice is one block. The run starts at 0 and reaches 3, so the length is 3 - 0 + 1 = 4.
Step by step
- Start
resultat 1. A single element is already a run of length 1. It is a variable because it grows. - Sort with
sort.Ints, which sorts a[]intin place. - Set
i := 0and loop withfor i < len(nums). Go has nowhile. Aforwith only a condition does the same job. The loop doesn't usei++; the last line of its body movesito where the next block begins. - Record
currentValue := nums[i]. It is the last value of the run so far. Since the slice is sorted, remembering this one number is enough. - Set
j := i + 1, the next element to try to add. - Run an inner loop while
j < len(nums) && nums[j] <= currentValue+1. Such an element is either a duplicate ofcurrentValueor the next value. Anything bigger is a gap, so the block ends. The bounds check comes first so thatnums[j]is never read past the end:&&stops evaluating as soon as its left side is false. - Extend on the next value. If
nums[j] == currentValue+1, incrementcurrentValue. A duplicate fails this test and changes nothing. - Advance
jon every pass, for duplicates and new values alike. - Update
resultwith the larger of its value andcurrentValue-nums[i]+1. - Jump with
i = j. Everything beforejbelongs to the block just measured. - Return
resultafter the outer loop.
The solution in Go
func longestConsecutive(nums []int) int {
result := 1
sort.Ints(nums)
i := 0
for i < len(nums) {
currentValue := nums[i]
j := i + 1
for j < len(nums) && nums[j] <= currentValue+1 {
if nums[j] == currentValue+1 {
currentValue++
}
j++
}
result = max(result, currentValue-nums[i]+1)
i = j
}
return result
}
Go details:
maxis a built-in function since Go 1.21, so it needs no import and works on any ordered type. The file does needimport "sort".- The condition
j < len(nums) && nums[j] <= currentValue+1relies on short-circuit evaluation. Swapping the two operands would panic with an index out of range whenjreaches the end. sort.Intsreorders the caller's slice, because slices share their backing array. After calling the function on[3, 1, 2], the caller's slice reads[1 2 3].
Complexity
Time: O(n log n). The sort dominates. The two nested loops look quadratic, but j only moves forward and i jumps to j after each block, so every element is passed over once: O(n) for the scan.
Space: O(1) extra. The function keeps a handful of integers and sorts in place.
Edge cases and common mistakes
- Duplicates.
[7, 7, 7]returns 1. Counting every element in a block instead of the value range would give 3. - Duplicates inside a run. The condition uses
<=and not==, so[1, 2, 2, 3]stays one block and the answer is 3. With==, the scan would stop at the second 2. - A gap.
[1, 2, 4]returns 2: the jump from 2 to 4 ends the block. - Negative numbers. They work unchanged:
[-2, -1, 0, 5, 6]returns 3. - An empty slice. The solution returns 1 because
resultstarts at 1 and the loop never runs. The true answer for no values would be 0, so guard against that if empty input is possible. - Not moving
iforward. If the last line werei++, the same block would be measured again from each of its elements, and the scan would lose its linear cost.
Try in the editor above: the two examples should both give 4, [5] should give 1 and [1, 2, 4] should give 2.