Two-ink run
Find the longest block of print jobs a two-cartridge press can run without a swap, by growing a window and shrinking it only when a third ink appears.
A small press holds two ink cartridges at a time. Swapping one out stops the line, so the operator wants the longest run of queued jobs that needs no swap.
The problem
The queue is a string of ink codes, one character per job, in the order the jobs
will print: c, m, y, k for the four inks the shop stocks. The press can
hold any two of them, chosen freely before the run starts.
Find the length of the longest contiguous block of jobs that uses at most two distinct inks. Jobs cannot be reordered, and a block using only one ink is fine — the second cartridge simply sits idle.
Input. queue — a string of ink codes, possibly empty.
Output. An integer, the length of the longest block using at most two distinct inks.
Example.
queue = "ccmmyc" -> 4
The block ccmm runs on cyan and magenta. Adding the y after it would need a
third cartridge, and the tail myc needs three inks in three jobs.
A second example, where the best block is not at the front:
queue = "mccmmyyyym" -> 7
The opening mccmm is five jobs on two inks, but mmyyyym starting at job 3 is
seven on magenta and yellow. A method that grows a block from the start and stops
at the first violation reports 5.
Constraints.
0 <= len(queue) <= 10^5- Every character is one of
c,m,y,k.
Hints
Hint 1
Think of a window over the queue with a left and a right edge. What is the only event that makes a legal window illegal?
Hint 2
When a third ink arrives you have to pull the left edge in. How far? Until one of the three inks has left the window entirely.
Hint 3
The left edge never needs to move backwards. Once a start position has been ruled out for one right edge, it stays ruled out for every later one.
Approach
Brute force
Try every start, extend to every end, and track the distinct inks. That is
n * (n + 1) / 2 blocks — 5 * 10^9 on a queue of 10^5 jobs — and most of the
work re-derives what the previous start already established.
The insight
If a block starting at job i is illegal, so is every block starting at i
that ends later, so the left edge only ever moves forward.
Adding a job can only raise the distinct count and removing one can only lower
it, so the legality of a window is monotone in its left edge for a fixed right
edge. That is the precondition the window needs: both pointers sweep the queue
once, in the same direction, and the pair (left, right) is always the longest
legal window ending at right.
Algorithm
- Keep a map from ink code to how many jobs in the window use it.
- Move
rightacross the queue, incrementing the entry for that job's ink. - While the map holds three keys, decrement the count for the job at
left, delete the key if the count reaches 0, and advanceleft. - Record
right - left + 1if it beats the best so far. - Return the best.
Complexity
Time O(n) — right advances n times and left never overtakes it, so the
shrink loop runs at most n times in total. Space O(1): the map holds three
keys at its largest.
Solution
"""Two-ink run — longest window holding at most two distinct ink codes."""
def solve(queue):
loaded = {} # ink code -> how many jobs in the window use it
left = 0
longest = 0
for right, ink in enumerate(queue):
loaded[ink] = loaded.get(ink, 0) + 1
while len(loaded) > 2: # invariant: after this loop the window fits the press
leaving = queue[left]
loaded[leaving] -= 1
if loaded[leaving] == 0:
del loaded[leaving]
left += 1
longest = max(longest, right - left + 1)
return longestThe cases that ran
TESTS = [
(("ccmmyc",), 4),
(("mccmmyyyym",), 7),
(("kkkk",), 4),
(("cmyk",), 2),
(("",), 0),
(("c",), 1),
]Pitfalls
- Shrinking with
ifinstead ofwhile. One removal is often not enough: from the windowccm, addingyneeds three jobs dropped before a cartridge is freed, and a singleifleaves the illegal windowcmy. - Leaving a zero count in the map.
len(map)stays at 3 forever,leftchasesrightto the end, and"mccmmyyyym"reports 1 instead of 7. - Rebuilding a set over the window each step. It gives the right answer and
restores the
n * nrunning time the window was meant to remove. - Returning
leftor the window contents. The answer is a length; on an empty queue it is 0, and no index exists to return.
Variants
- Exactly k stops — the same at-most-k window, generalised past two and counted rather than measured.