32. Longest Increasing Confidence Streak
Problem
Fin's model gets re-benchmarked weekly, producing an average confidence score for that week's release. As a signal of sustained improvement (rather than a single lucky week), find the length of the longest streak of strictly increasing scores across the sequence of weekly releases. The streak does not need to be made of consecutive weeks — just weeks in increasing order.
This is the classic Longest Increasing Subsequence problem. It should be solved with the O(n log n) patience-sorting/binary-search version, not the O(n²) DP version.
Source: src/32_most_common_query_pattern.py
def longest_increasing_streak(scores: list[float]) -> int:
>>> longest_increasing_streak([0.1, 0.3, 0.2, 0.4, 0.35, 0.5])
4
>>> longest_increasing_streak([0.5, 0.4, 0.3, 0.2, 0.1])
1
Step-by-Step Approach
- Maintain a working array
tails, wheretails[i]is the smallest possible last value of any increasing subsequence of lengthi + 1found so far. - Walk through the scores one at a time.
- For each score
x, binary-searchtailsfor the leftmost position wherexcould be inserted to keep it sorted (bisect_left). - If that position is past the end of
tails,xextends the longest subsequence found so far — append it. - Otherwise,
xreplaces the value at that position, sincexis a strictly smaller (or equal) tail for a subsequence of that length, which keeps future extensions more flexible. - After processing every score, the length of
tailsis the answer.
The key insight is that tails is not literally the longest subsequence itself,
just a proxy — its length always equals the true LIS length, and keeping each tail as small
as possible maximizes future extension opportunities. Using bisect_left instead
of bisect_right is what enforces "strictly increasing" instead of "non-decreasing."
Reference solution
from bisect import bisect_left
def longest_increasing_streak(scores: list[float]) -> int:
# patience sorting: tails[i] = smallest possible tail of an increasing
# subsequence of length i+1; bisect_left keeps it strict, O(n log n) time, O(n) space
tails: list[float] = []
for x in scores:
# leftmost insert point; enforces strict increase
idx = bisect_left(tails, x)
if idx == len(tails):
# x extends the longest streak so far
tails.append(x)
else:
# x is a smaller tail for that streak length
tails[idx] = x
# length of tails == true LIS length, not its contents
return len(tails)
Key Functions & Tricks
bisect.bisect_left(a, x)— leftmost sorted-insert index forxina, O(log n).- "Tails" array (patience sorting) —
tails[i]is the smallest last value of any length-i+1increasing subsequence found so far. - Insertion point as streak length — append if
xis past the end, else overwrite the first tail>= x. bisect_leftvsbisect_right— left enforces strictly increasing; right would allow non-decreasing.
How to Recognize This Pattern
Signals: "longest subsequence" (not substring/subarray — order matters but elements don't
need to be contiguous) combined with a monotonicity condition ("increasing," "non-decreasing").
If an interviewer follows up wanting O(n log n) instead of O(n²), that's the tell for the
tails + binary search technique over classic DP. Variations: (1) "non-decreasing" instead of
strictly increasing — switch to bisect_right; (2) reconstructing the actual
subsequence, not just its length — requires tracking predecessor indices alongside the
tails array, since the tails array itself gets overwritten and isn't the real answer. A
common pitfall is trying to read the actual increasing subsequence directly out of the
final tails array — it is not guaranteed to be a valid subsequence of the
input, only its length is meaningful.