LeetCode 228 - Summary Ranges
The problem gives us a sorted array of unique integers and asks us to summarize consecutive values into compact range strings. A range represents a continuous sequence of integers.
Difficulty: 🟢 Easy
Topics: Array
Solution
LeetCode 228, Summary Ranges
Problem Understanding
The problem gives us a sorted array of unique integers and asks us to summarize consecutive values into compact range strings.
A range represents a continuous sequence of integers. For example, the numbers [4,5,6] can be represented as "4->6" because every integer between 4 and 6 exists in the array. A single isolated value such as 7 should simply be represented as "7".
The important detail is that the output must cover every number exactly once, without including numbers that are not present in the array. Since the input is already sorted and contains no duplicates, consecutive ranges can be identified by checking whether the next number is exactly one greater than the current number.
For example:
[0,1,2]becomes"0->2"[7]becomes"7"[4,5]becomes"4->5"
The input array can be empty, which means the answer should also be an empty list.
The constraints are very small, with at most 20 numbers, but the goal is still to design a clean and efficient solution. The sorted and unique guarantees are extremely important because they simplify the problem significantly:
- Since the array is sorted, consecutive numbers always appear next to each other.
- Since the values are unique, we never need to worry about duplicates interfering with range detection.
Several edge cases are important:
- An empty array should return
[]. - A single element array should return a list containing only that number as a string.
- Arrays where every number is consecutive should produce exactly one range.
- Arrays where no numbers are consecutive should produce only single-number ranges.
- Negative values must work correctly as well.
Approaches
Brute Force Approach
A brute force approach would attempt to build ranges by repeatedly checking every possible continuation for each starting point. For every number, we could scan forward until the sequence breaks, then restart from the next unused number.
This works because the input is sorted, so consecutive numbers appear together. However, the implementation can become inefficient if we repeatedly rescan portions of the array or perform redundant comparisons.
For example, if we start at index i, we may scan all the way to the end to find the range boundary. Then later iterations may revisit many of the same elements again.
Although the constraints are tiny in this problem, this style of repeated scanning can lead to quadratic behavior in larger inputs.
Optimal Approach
The key observation is that each number only needs to be processed once.
Because the array is sorted and unique, a range ends exactly when the next number is not equal to the current number plus one.
This means we can scan the array from left to right using a single pass:
- Record the start of the current range.
- Continue moving forward while numbers remain consecutive.
- Once the sequence breaks, finalize the current range string.
- Start a new range from the next number.
This approach is efficient because every element is visited only once.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n²) | O(1) excluding output | Repeatedly rescans portions of the array |
| Optimal | O(n) | O(1) excluding output | Single pass through the array |
Algorithm Walkthrough
- Create an empty result list that will store the final range strings.
- Handle the array from left to right using an index pointer. Each iteration begins by marking the current number as the start of a new range.
- Store the current value as
range_start. This value will remain fixed while we attempt to extend the range. - Move the index forward while the next number is consecutive. Two numbers are consecutive if:
nums[i + 1] == nums[i] + 1
As long as this condition holds, the current range continues.
5. When the consecutive sequence ends, the current number becomes the end of the range.
6. If the start and end are the same value, append a single number string such as "7".
7. Otherwise, append a range string such as "4->6".
8. Continue scanning from the next unprocessed number until the entire array has been handled.
Why it works
The algorithm maintains the invariant that every processed element belongs to exactly one completed range.
Because the array is sorted and unique, every consecutive sequence appears as one contiguous block. The inner loop expands the range until the sequence breaks, guaranteeing that the produced range is maximal. Since the algorithm advances past all processed elements before starting the next range, no number is skipped or duplicated.
Python Solution
from typing import List
class Solution:
def summaryRanges(self, nums: List[int]) -> List[str]:
result: List[str] = []
n = len(nums)
index = 0
while index < n:
range_start = nums[index]
while (
index + 1 < n
and nums[index + 1] == nums[index] + 1
):
index += 1
range_end = nums[index]
if range_start == range_end:
result.append(str(range_start))
else:
result.append(f"{range_start}->{range_end}")
index += 1
return result
The implementation begins by initializing an empty result list and an index pointer.
The outer while loop processes one range at a time. At the beginning of each iteration, the current value becomes the start of the range.
The inner loop extends the range as long as consecutive numbers continue appearing. Since the array is sorted, consecutive values must appear immediately next to each other.
After the loop finishes, the current index points to the final value in the range. The code then decides whether the range contains a single value or multiple values.
If both ends are equal, the output format is just the number itself. Otherwise, the range is formatted using "start->end".
Finally, the index moves forward to begin processing the next range.
Go Solution
package main
import "strconv"
func summaryRanges(nums []int) []string {
result := []string{}
n := len(nums)
index := 0
for index < n {
rangeStart := nums[index]
for index+1 < n && nums[index+1] == nums[index]+1 {
index++
}
rangeEnd := nums[index]
if rangeStart == rangeEnd {
result = append(result, strconv.Itoa(rangeStart))
} else {
rangeString := strconv.Itoa(rangeStart) + "->" + strconv.Itoa(rangeEnd)
result = append(result, rangeString)
}
index++
}
return result
}
The Go implementation follows exactly the same logic as the Python solution.
One notable difference is string conversion. Go requires strconv.Itoa() to convert integers into strings.
The result is stored in a slice of strings. An empty input naturally returns an empty slice because the loop never executes.
The integer comparison is safe because the constraints fit within Go's standard integer range on LeetCode systems.
Worked Examples
Example 1
Input:
nums = [0,1,2,4,5,7]
Initial state:
| Variable | Value |
|---|---|
| result | [] |
| index | 0 |
First Range
| Step | index | Current Value | Action |
|---|---|---|---|
| Start range | 0 | 0 | range_start = 0 |
| Check next | 1 | 1 | consecutive |
| Check next | 2 | 2 | consecutive |
| Stop | 2 | 2 | next value is 4 |
Range becomes:
0->2
Result:
["0->2"]
Second Range
| Step | index | Current Value | Action |
|---|---|---|---|
| Start range | 3 | 4 | range_start = 4 |
| Check next | 4 | 5 | consecutive |
| Stop | 4 | 5 | next value is 7 |
Range becomes:
4->5
Result:
["0->2", "4->5"]
Third Range
| Step | index | Current Value | Action |
|---|---|---|---|
| Start range | 5 | 7 | single value |
| Stop | 5 | 7 | end of array |
Range becomes:
7
Final result:
["0->2", "4->5", "7"]
Example 2
Input:
nums = [0,2,3,4,6,8,9]
First Range
| Step | index | Current Value | Action |
|---|---|---|---|
| Start range | 0 | 0 | single value |
| Stop | 0 | 0 | next value is 2 |
Result:
["0"]
Second Range
| Step | index | Current Value | Action |
|---|---|---|---|
| Start range | 1 | 2 | range_start = 2 |
| Check next | 2 | 3 | consecutive |
| Check next | 3 | 4 | consecutive |
| Stop | 3 | 4 | next value is 6 |
Result:
["0", "2->4"]
Third Range
| Step | index | Current Value | Action |
|---|---|---|---|
| Start range | 4 | 6 | single value |
| Stop | 4 | 6 | next value is 8 |
Result:
["0", "2->4", "6"]
Fourth Range
| Step | index | Current Value | Action |
|---|---|---|---|
| Start range | 5 | 8 | range_start = 8 |
| Check next | 6 | 9 | consecutive |
| Stop | 6 | 9 | end of array |
Final result:
["0", "2->4", "6", "8->9"]
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Every element is processed exactly once |
| Space | O(1) excluding output | Only a few variables are used |
The algorithm performs a single linear scan through the array. Although there is a nested loop, the index only moves forward, never backward. This means each element participates in at most one iteration of the inner loop.
The extra memory usage is constant because the algorithm only stores a few tracking variables. The output list itself is not counted in auxiliary space complexity.
Test Cases
from typing import List
class Solution:
def summaryRanges(self, nums: List[int]) -> List[str]:
result: List[str] = []
n = len(nums)
index = 0
while index < n:
range_start = nums[index]
while (
index + 1 < n
and nums[index + 1] == nums[index] + 1
):
index += 1
range_end = nums[index]
if range_start == range_end:
result.append(str(range_start))
else:
result.append(f"{range_start}->{range_end}")
index += 1
return result
solution = Solution()
assert solution.summaryRanges([0,1,2,4,5,7]) == ["0->2","4->5","7"] # basic mixed ranges
assert solution.summaryRanges([0,2,3,4,6,8,9]) == ["0","2->4","6","8->9"] # alternating singles and ranges
assert solution.summaryRanges([]) == [] # empty input
assert solution.summaryRanges([5]) == ["5"] # single element
assert solution.summaryRanges([1,2,3,4]) == ["1->4"] # one large range
assert solution.summaryRanges([1,3,5,7]) == ["1","3","5","7"] # no consecutive numbers
assert solution.summaryRanges([-3,-2,-1,1,2,4]) == ["-3->-1","1->2","4"] # negative values
assert solution.summaryRanges([0,1,3,4,5,7,8,10]) == ["0->1","3->5","7->8","10"] # multiple separate ranges
assert solution.summaryRanges([-2147483648,-2147483647,2147483647]) == ["-2147483648->-2147483647","2147483647"] # integer boundary values
| Test | Why |
|---|---|
[0,1,2,4,5,7] |
Validates normal mixed behavior |
[0,2,3,4,6,8,9] |
Tests alternating singletons and ranges |
[] |
Ensures empty input works |
[5] |
Tests single element handling |
[1,2,3,4] |
Ensures one full range is formed |
[1,3,5,7] |
Ensures isolated values remain separate |
[-3,-2,-1,1,2,4] |
Verifies negative number handling |
[0,1,3,4,5,7,8,10] |
Tests multiple disjoint ranges |
| Extreme integer values | Confirms boundary correctness |
Edge Cases
Empty Array
An empty input is one of the easiest places for implementations to fail if they assume at least one element exists.
For example, accessing nums[0] without checking the array length would cause an index error. This implementation avoids that problem because the outer loop only executes when index < n. If n is zero, the loop body never runs, and the function correctly returns an empty list.
Single Element Array
When the array contains only one value, the algorithm must return a single-number string instead of a range format.
For example:
[7] -> ["7"]
A common bug is always formatting output as "start->end", which would incorrectly produce "7->7". The implementation explicitly checks whether the range start and range end are equal before deciding how to format the result.
No Consecutive Numbers
Arrays like [1,3,5,7] contain no consecutive pairs at all.
In this case, every number should become its own separate range. The inner loop immediately stops for each element because the next number is never equal to the current number plus one.
The implementation naturally handles this because every iteration creates a one-element range.
Entire Array Is One Range
Arrays like [1,2,3,4,5] test whether the algorithm can correctly extend a range all the way to the end of the array.
A common mistake is failing to finalize the last range after the loop finishes. This implementation avoids that issue because the range is added immediately after the inner loop ends, even if the sequence continued until the final element.