Stamping The Sequence looks impossible forwards — overlapping stamps overwrite each other, so you cannot reason about the final string left to right. The trick is one of the most elegant moves in interviews: run the process in reverse.
Problem. You start with a string of all ? the same length as target. In one move you replace any
length-len(stamp) window with stamp, overwriting whatever was there. Return any sequence of starting
indices (at most 10 * len(target) of them) that turns the all-? string into target, or [] if it is impossible.
Example: stamp = "ab", target = "abab" → answer [2, 0] (stamp at 2, then at 0, rebuilds abab).
The slow way first
Forwards, every choice of where to stamp affects every later choice, because a later stamp can overwrite an earlier one. There is no clean greedy rule and the search tree explodes. Trying to build the target by stamping is a combinatorial mess.
The question to ask: what does the very last stamp look like? Whatever it was, it left a clean, untouched copy of stamp somewhere in target. So the last stamp is easy to spot — and if we peel it off, the next-to-last becomes easy too.
The idea: un-stamp instead of stamp
Work backwards. Scan target for any window that equals the stamp, where an already-erased ? counts as a wildcard match. Replace that window with ? and record its start index. Repeat until the whole string is ?. The starts we recorded are the un-stamp order; reverse them to get the build order.
The key insight: matching against ? as a wildcard lets later (overlapping) stamps be erased even when earlier ones already chewed up part of the window.
Walk through it
Step through the animation. With target = "abab", the window at index 0 matches "ab", so we erase it to "??ab" and record 0. The window at index 2 still matches "ab", so we erase it to "????" and record 2. Everything is ?, so we stop. We un-stamped in order [0, 2]; reversed, the build order is [2, 0].
Pseudocode
t = list(target); stamps = []; total erased = 0
while not everything is "?":
stamped this pass = false
for each window start i:
if window matches stamp (treating ? as wildcard, and not already all ?):
erase the window to ?, add its newly-erased count to total
record i; mark stamped this pass = true
if no window stamped this pass:
return [] # impossible
return recorded starts reversedThe Python solution
def moves_to_stamp(stamp, target):
t = list(target)
stamps, total = [], 0
while total < len(t):
stamped = False
for i in range(len(t) - len(stamp) + 1):
if can_stamp(t, stamp, i):
total += do_stamp(t, stamp, i)
stamps.append(i)
stamped = True
if not stamped:
return []
return stamps[::-1]tis the mutable target;totalcounts how many characters have been turned into?.can_stampreturns true if windowihas at least one non-?char and every non-?char equals the stamp —?positions match anything.do_stamperases the window to?and returns how many newly erased characters it added, which we accumulate intototal.- If a full pass stamps nothing but the string is not all
?, the target is unreachable, so we return[]. - Line 13 reverses the recorded starts: un-stamp order reversed is the order to build the target.
Complexity
| Case | Time | Notes |
|---|---|---|
| Each pass | O(n * m) (moderate) | n windows, m = len(stamp) per check |
| Number of passes | O(n / m) (moderate) | each pass erases at least m chars |
| Overall | O(n^2) (slow) | n = len(target) |
O(n) (moderate)Every pass erases at least one stamp worth of characters, so there are at most O(n/m) passes, each scanning O(n*m) work — O(n^2) total. The space is the mutable copy of the target plus the recorded starts.
When this pattern shows up
When building forwards looks hopeless because later actions overwrite earlier ones, ask whether the last action is easy to identify. If so, peel actions off in reverse and flip the answer at the end. Reverse-construction shows up in stamping, certain interval merges, and "undo to a known state" problems.
A window that is already all ? must not count as a valid stamp — otherwise you loop forever erasing
nothing. Require at least one non-? character before stamping, and stop when a full pass changes nothing.
Practice
For stamp = 'ab', target = 'abab', after erasing the window at index 0 the board is '??ab'. Which window do we erase next, and what does the board become?
1. Why do we solve this problem backwards instead of forwards?
2. When un-stamping, what does a '?' in a window count as?
3. Why must we reverse the recorded list of start indices at the end?
4. When do we return [] (impossible)?