No repeats on air
Find the longest unbroken stretch of a radio queue with no artist played twice, moving the left edge only forward.
A repeat inside one stretch breaks the station's rule. Where the next legal stretch starts is the whole question, and the tempting answer is wrong.
The problem
A community station runs an automated queue: a fixed list of tracks played in order, each tagged with the artist's short code. The programming rule says an artist may not appear twice inside one unbroken stretch of airtime — a station ident breaks the stretch and the count starts again.
The scheduler wants the longest stretch of consecutive tracks it can play before it is forced to drop an ident in: the longest run of consecutive tracks with no artist code repeated.
Input. queue — a list of artist codes, one per track, in play order.
Output. The length of the longest run of consecutive tracks containing no code twice.
Example.
queue = ["moss", "kestrel", "moss", "briar", "kestrel", "vole"] -> 4
The run moss, briar, kestrel, vole has four distinct artists. Extending it left
picks up the earlier kestrel, already in the run.
A second example, where a repeat outside the current stretch must be ignored:
queue = ["lark", "swift", "swift", "lark"] -> 2
queue = ["heron", "heron", "heron"] -> 1
The doubled swift restarts the stretch at index 2; the later lark was last
heard at index 0, already behind that start, so it forces nothing. The answer is
swift, lark. One artist on repeat gives stretches of one.
Constraints.
0 <= len(queue) <= 10^5- Each code is 1 to 20 lowercase letters.
- The queue may be empty, in which case the answer is
0.
Hints
Hint 1
Walk the queue a track at a time, holding the current legal stretch. When the next track breaks it, where can the stretch now start?
Hint 2
Knowing where each artist was last heard turns "does this break the stretch" into one lookup.
Hint 3
A previous airing only matters if it is inside the current stretch. Compare it against where the stretch starts before you move anything.
Approach
Brute force
Take every start, extend while the artists stay distinct, keep the longest. For 10⁵ tracks that is up to 5 billion membership checks, most re-verifying a prefix an earlier start already cleared.
The insight
When a repeat appears, the next legal stretch cannot begin earlier than one track past that artist's previous airing — so the left edge only ever moves forward.
A stretch stays legal when you trim tracks off its front — dropping a track can never create a repeat — so each right edge has a single earliest legal start, and that start is non-decreasing as the right edge advances. One forward sweep of each edge visits every candidate stretch.
The subtlety is that a previous airing may already sit behind the current start, in which case it is outside the stretch and irrelevant. Jumping the start to it would be moving backwards, which the argument forbids.
Algorithm
- Keep
last, a map from artist code to its most recent index, plusstart = 0andbest = 0. - For each index
iwith codec: - If
cis inlastandlast[c] >= start, setstart = last[c] + 1. - Set
last[c] = i. - Update
bestwithi - start + 1. - Return
best.
Complexity
Time O(n) — one pass, one average-constant map lookup and update per track.
Space O(d) for d distinct artist codes; the map never grows past that.
Solution
"""No repeats on air — longest repeat-free stretch via two forward-only edges."""
def solve(queue):
last_heard = {}
start = 0
best = 0
for i, code in enumerate(queue):
# Invariant: queue[start..i-1] has no repeated code. A previous airing
# only breaks that if it is at or after start; an older one is outside
# the stretch, and moving start back to it would undo settled work.
if code in last_heard and last_heard[code] >= start:
start = last_heard[code] + 1
last_heard[code] = i
if i - start + 1 > best:
best = i - start + 1
return bestThe cases that ran
TESTS = [
((["moss", "kestrel", "moss", "briar", "kestrel", "vole"],), 4),
((["lark", "swift", "swift", "lark"],), 2),
((["heron", "heron", "heron"],), 1),
(([],), 0),
((["fox", "owl", "doe", "fox", "fox"],), 3),
((["fern", "gull", "fern", "gull", "wren"],), 3),
((["solo"],), 1),
]Pitfalls
- Moving the start to
last[c] + 1without checking it is inside the stretch. On["lark", "swift", "swift", "lark"]the finallarkwas last heard at index 0, and jumping there sets the start back to 1 — reporting 3 for a stretch that playsswifttwice. Guard withlast[c] >= start, or take a maximum. - Clearing the map and restarting at every repeat. Correct and quadratic: a
queue like
a b c d a b c d …rebuilds the whole prefix every time. - Returning the final stretch instead of the longest seen. On
["fox", "owl", "doe", "fox", "fox"]the queue ends on a stretch of one while the answer is three. Record the best on every step, seeded at0so an empty queue answers0instead of failing.
Variants
- Cones on the loom — the same
window, with "no repeats" loosened to "at most
kdistinct", which needs counts rather than a last-seen index. - Shortest soak — the same forward-only edges used to find the shortest qualifying window instead of the longest legal one.