002. Running Minimum and Maximum
Problem
For each position in a list of integers, report the smallest and largest values in the prefix ending there. Return (minimums, maximums), two newly allocated lists in input order. Do not mutate nums. For empty input return two empty lists.
Examples
Example 1
running_extrema([4, 2, 5, 1])
=> ([4, 2, 2, 1], [4, 4, 5, 5])Each column describes the same prefix.
Example 2
running_extrema([])
=> ([], [])Both output lists are empty.
Example 3
running_extrema([-4, -2])
=> ([-4, -4], [-4, -2])Zero is not an artificial maximum.
Reveal the worked solution and reasoning
Why this mechanism works
A new value can change the minimum, the maximum, both on the first step, or neither. These are independent reductions over the same prefix, so one scan can maintain both. Initializing from the first actual value avoids assuming that zero belongs to the data.
State and boundaries
low and high summarize the processed prefix. minimums and maximums contain one committed result per consumed value.
Protocol
- Return two empty lists when there is no first value.
- Initialize both extrema from nums[0].
- Extend both extrema with each incoming value.
- Append both answers after updating.
Visualization
value: 4 2 5 1
low: 4 2 2 1
high: 4 4 5 5Python implementation
def running_extrema(nums):
if not nums:
return [], []
low = high = nums[0]
minimums, maximums = [], []
for value in nums:
low = min(low, value)
high = max(high, value)
minimums.append(low)
maximums.append(high)
return minimums, maximums
Worked execution
running_extrema([4, 2, 5, 1])
L2 running_extrema: if not nums: nums=[4, 2, 5, 1] L4 running_extrema: low = high = nums[0] nums=[4, 2, 5, 1]; low=4; high=4 L5 running_extrema: minimums, maximums = [], [] nums=[4, 2, 5, 1]; low=4; high=4; minimums=[]; maximums=[] L6 running_extrema: for value in nums: nums=[4, 2, 5, 1]; low=4; high=4; minimums=[]; maximums=[]; value=4 L9 running_extrema: minimums.append(low) nums=[4, 2, 5, 1]; low=4; high=4; minimums=[4]; maximums=[]; value=4 L10 running_extrema: maximums.append(high) nums=[4, 2, 5, 1]; low=4; high=4; minimums=[4]; maximums=[4]; value=4 L6 running_extrema: for value in nums: nums=[4, 2, 5, 1]; low=4; high=4; minimums=[4]; maximums=[4]; value=2 L7 running_extrema: low = min(low, value) nums=[4, 2, 5, 1]; low=2; high=4; minimums=[4]; maximums=[4]; value=2 ... 6 intermediate state changes omitted ... L6 running_extrema: for value in nums: nums=[4, 2, 5, 1]; low=2; high=5; minimums=[4, 2, 2]; maximums=[4, 4, 5]; value=1 L7 running_extrema: low = min(low, value) nums=[4, 2, 5, 1]; low=1; high=5; minimums=[4, 2, 2]; maximums=[4, 4, 5]; value=1 L9 running_extrema: minimums.append(low) nums=[4, 2, 5, 1]; low=1; high=5; minimums=[4, 2, 2, 1]; maximums=[4, 4, 5]; value=1 L10 running_extrema: maximums.append(high) nums=[4, 2, 5, 1]; low=1; high=5; minimums=[4, 2, 2, 1]; maximums=[4, 4, 5, 5]; value=1
Result: ([4, 2, 2, 1], [4, 4, 5, 5]). Each column describes the same prefix.
running_extrema([])
L2 running_extrema: if not nums: nums=[]
Result: ([], []). Both output lists are empty.
Why the solution is correct
The first value is both extrema of a singleton prefix. min(low, value) and max(high, value) preserve the definitions when the prefix grows. Appending only after both updates keeps the two output positions aligned.
Time and space
O(n) time; O(1) working state plus O(n) returned storage.
Common mistakes
Initializing low to zero fails on all-positive input. Using elif between independent extrema updates can mishandle initialization or future variants.
Reasoning card
State: low and high summarize the processed prefix. minimums and maximums contain one committed result per consumed value.
Invariant: Both running values describe exactly the processed prefix.
Decision to rehearse: Coordinate two independent accumulators in one pass.
Finish: Append both answers after updating
Transfer exercise
Also return the running range high-low, without rescanning any prefix.