38. Longest Increasing Run of a Training Metric
Problem
A training-run dashboard logs a scalar metric, such as eval accuracy, once per logged step. To flag genuinely sustained improvement as opposed to noisy single-step jumps, an engineer wants the length of the longest run of consecutive steps over which the metric strictly increased step to step.
Given the metric logged in step order, return the length (in steps) of the longest contiguous run where each value is strictly greater than the one before it. A run of length 1 (a single value with no increase on either side) still counts. This must run in O(n) time with O(1) extra space.
Source: src/38_longest_increasing_metric_run.py
def longest_increasing_run(values: list[float]) -> int:
...
Examples:
>>> longest_increasing_run([0.2, 0.4, 0.3, 0.5, 0.6, 0.65])
4
>>> longest_increasing_run([1.0, 1.0, 1.0])
1
Step-by-Step Approach
- Handle the empty-input edge case first: with no logged steps, there is no run at all, so the answer is 0.
- Initialize both a "current run length" counter and a "best run length seen" tracker to 1, since any single value is trivially a run of length 1.
- Walk the values from the second element onward, comparing each value to the one immediately before it.
- If the current value is strictly greater than the previous one, the run is still increasing: extend the current run length by 1.
- Otherwise (equal or decreasing), the run has broken: reset the current run length back to 1, starting a fresh run at this value.
- After each step, update the best-seen tracker if the current run length has surpassed it, so the final answer reflects the longest run anywhere in the sequence, not just the one ending at the last element.
The key insight is that you never need to look backward more than one step — each comparison only depends on the immediately preceding value, so the whole scan is a single O(n) pass with two scalar counters and no auxiliary array.
Reference solution
def longest_increasing_run(values: list[float]) -> int:
if not values:
return 0
# single left-to-right scan: extend current run while strictly increasing,
# reset to 1 on a non-increase, track the best seen. O(n) time, O(1) space.
best = current = 1
for i in range(1, len(values)):
if values[i] > values[i - 1]:
current += 1
else:
current = 1
best = max(best, current)
return best
Key Functions & Tricks
- Single-pass counter reset — the classic "Kadane-style" trick of resetting a running counter to a base value on a broken condition instead of recomputing from scratch
values[i] > values[i - 1]— strict inequality means a plateau (equal consecutive values) breaks the run, not just a decreasebest = max(best, current)— must be updated on every iteration, since the longest run may end before the final element- Empty-list guard — returning 0 for no input avoids indexing into an empty sequence when seeding
best/current
How to Recognize This Pattern
The signal is "find the longest contiguous run in a sequence satisfying some pairwise condition between adjacent elements" (strictly increasing, strictly decreasing, alternating, or a fixed step size). Whenever the condition only depends on comparing each element to its immediate neighbor, a single forward scan with a reset-on-break counter solves it in O(n), no auxiliary data structure needed. Common variations include allowing non-strict increases (change the comparison to >=), finding the longest strictly monotonic run in either direction, or finding the longest run within a fixed tolerance instead of a strict inequality. A common pitfall is confusing this with the *longest increasing subsequence* problem, which allows skipping elements and needs O(n log n) DP with binary search — here the run must be contiguous, which is exactly what keeps this one to O(n) with no extra space.