Skip to content

228. Summary Ranges (Easy)

Problem

Given a sorted array of unique integers, return the smallest sorted list of ranges that covers every input value exactly once and includes no value that was absent from the input.

Format a range from a to b as:

  • "a->b" when a != b.
  • "a" when a == b.

Example 1

Input: nums = [0,1,2,4,5,7]
Output: ["0->2","4->5","7"]

The consecutive runs are [0,1,2], [4,5], and [7].

Example 2

Input: nums = [0,2,3,4,6,8,9]
Output: ["0","2->4","6","8->9"]

Constraints

  • 0nums.length200 \leq \text{nums.length} \leq 20
  • 231nums[i]2311-2^{31} \leq \text{nums}[i] \leq 2^{31} - 1
  • Every value is unique.
  • nums is sorted in ascending order.

LeetCode 228 · Top Interview 150 link · Easy

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: Expand runs with a hash set

Put every value in a set. A value begins a range when its predecessor is absent. From each range start, keep incrementing the end while the next integer exists in the set.

This approach works, but it ignores the most useful input guarantee: the array is already sorted. The set spends extra memory to recover adjacency that the input order gives us for free.

def summary_ranges(nums: list[int]) -> list[str]:
present = set(nums) # L1: store all values
ranges: list[str] = []
for value in nums: # L2: inspect each value
if value - 1 in present: # L3: not a range start
continue
end = value
while end + 1 in present: # L4: expand this run
end += 1
ranges.append(str(value) if value == end # L5: format the run
else f"{value}->{end}")
return ranges

Where the time goes, line by line

Variables: n = len(nums), r = number of output ranges.

LinePer-call costTimes executedContribution
L1 build setO(n)O(n)1O(n)O(n)
L2-L3 outer scanO(1)O(1) averagennO(n)O(n)
L4 expand runsO(1)O(1) averageat most nn totalO(n)O(n)
L5 format rangesO(1)O(1) under 32-bit boundsrrO(r)O(r)

Each value is visited by the outer loop. Across all range starts, the expansion loops advance over at most n values in total.

Complexity

  • Time: O(n)O(n) average.
  • Space: O(n)O(n) for the set, excluding the output.
final class Solution {
func summaryRanges(_ nums: [Int]) -> [String] {
let values = Set(nums); var result: [String] = []
for start in nums where !values.contains(start - 1) { var end = start; while values.contains(end + 1) { end += 1 }; result.append(start == end ? "\(start)" : "\(start)->\(end)") }
return result
}
}

Approach 2: Close a range at each gap

The sorted order makes every decision local. Keep the start of the current run. At index i, the run ends when either i has reached the end of the array or nums[i] is not one greater than nums[i - 1].

The loop deliberately runs through i == nums.length. That final boundary closes the last open range without a separate block after the loop.

def summary_ranges(nums: list[int]) -> list[str]:
if not nums: # L1: empty input
return []
ranges: list[str] = []
start = nums[0] # L2: open first range
for i in range(1, len(nums) + 1): # L3: include end boundary
if i < len(nums) and nums[i] == nums[i - 1] + 1:
continue # L4: current run continues
end = nums[i - 1]
ranges.append(str(start) if start == end # L5: close current range
else f"{start}->{end}")
if i < len(nums):
start = nums[i] # L6: open next range
return ranges

Where the time goes, line by line

Variables: n = len(nums), r = number of output ranges.

LinePer-call costTimes executedContribution
L1-L2 initializeO(1)O(1)1O(1)O(1)
L3-L4 scan adjacent valuesO(1)O(1)nnO(n)O(n)
L5 format a closed rangeO(1)O(1) under 32-bit boundsrrO(r)O(r)
L6 open the next rangeO(1)O(1)at most r1r - 1O(r)O(r)

Complexity

  • Time: O(n)O(n), one pass over the input.
  • Auxiliary space: O(1)O(1), excluding the required output list.
  • Output space: O(r)O(r).

Try the optimal approach

idle
Click Run Python to execute. First run downloads Python (~10 MB, cached after that).
final class Solution {
func summaryRanges(_ nums: [Int]) -> [String] {
var result: [String] = [], index = 0
while index < nums.count { let start = nums[index]; while index + 1 < nums.count && nums[index + 1] == nums[index] + 1 { index += 1 }; let end = nums[index]; result.append(start == end ? "\(start)" : "\(start)->\(end)"); index += 1 }
return result
}
}

Why the scan is correct

At the start of each loop iteration, start is the first value in the current unreported consecutive run. If the next value is exactly one greater than the previous value, the run remains valid and no output is ready yet.

When a gap appears, nums[i - 1] is the final value in the run. Formatting [start, nums[i - 1]] covers every value in that run and nothing across the gap. Setting start = nums[i] opens the next run. The synthetic boundary at i == nums.length closes the last run, so every input value appears in exactly one output range.

How to recognize this pattern

  • The signal: The input is sorted and unique, and the output groups adjacent values into maximal consecutive runs.
  • The tempting wrong approach: Iterate through every integer from the minimum input value to the maximum.
  • The counterexample: [-2147483648, 2147483647] contains only two values but spans more than four billion integers.
  • Why it fails: Runtime would depend on the numeric gap instead of the input length.
  • The mental model: Open a range at the first value. Close it immediately before each gap.
ProblemBoundary being detected
128. Longest Consecutive SequenceStart of a consecutive run in an unsorted set
56. Merge IntervalsGap or overlap between sorted intervals
303. Range Sum Query - ImmutableQuery boundaries in a preprocessed array

Key takeaways

  • Sorted unique input turns range construction into a one-pass boundary scan.
  • A range closes when the next value is absent or the scan reaches the end.
  • The empty array needs an early return because there is no first range to open.
  • Iterate over input values, not every integer between the minimum and maximum.

References

  • Array Scans, the one-pass habit of carrying only the current range start.
  • Intervals, the boundary model used to represent each consecutive run compactly.