In the Stamping The Sequence problem, you are given two strings: stamp
and target
, both consisting of lowercase English letters.
stamp
string onto the target
string at any position, as long as each character in stamp
either matches the corresponding character in target
or the corresponding character in target
is already a question mark ('?'
).target
at that position is replaced with '?'
.target
string into all question marks ('?'
) using the minimum number of stamping moves.Constraints:
1 <= stamp.length <= target.length <= 1000
stamp
and target
consist of lowercase English letters.At first glance, it may seem natural to try stamping from left to right, greedily matching as many characters as possible. However, this can quickly run into situations where future required matches become impossible due to earlier moves.
Instead, consider the problem in reverse: what if we try to "unstamp" the target
string back to all question marks? If we can repeatedly find substrings that could have been produced by stamping (i.e., they match the stamp
or are already '?'
), we can replace them with question marks, and keep track of the positions. Reversing the order of these positions gives us a valid stamping sequence.
This reverse approach is more robust because it avoids getting stuck due to premature stamping, and ensures that every move is valid with respect to the current state of the target
.
The solution uses a greedy, reverse simulation. Here’s how we approach it step by step:
target
into a mutable list for easy modification.'?'
.target
are '?'
:stamp
could fit in target
:'?'
characters match stamp
):'?'
.
Why this works: By always stamping windows that can be fully erased (i.e., all their remaining letters match the stamp
or are already '?'
), we guarantee that every move is valid and that we eventually erase the entire target
.
Input: stamp = "abc"
, target = "ababc"
ababc
bab
matches abc
only if we ignore the first character. But at position 0: aba
matches abc
only in the first two letters.
abc
matches abc
exactly. We stamp at position 2, turning target into ab???
.
ab?
can be stamped, since ab?
matches abc
in the first two letters and the last is already '?'
. We stamp at position 0, turning target into ?????
.
O(N)
iterations, where N
is the length of target
.M
(length of stamp
), so O(N * M)
per iteration.O(N^2 * M)
in the worst case.O(N)
for storing the mutable target and the result positions.
The key insight for this problem is to work backwards, repeatedly "unstamping" any window that could have been produced by a valid stamping move. This ensures that every move is legal and avoids getting stuck. By tracking the positions and reversing the result, we obtain a valid stamping sequence that transforms target
into all question marks. This approach is both efficient and elegant, leveraging greedy matching and careful state tracking.
class Solution:
def movesToStamp(self, stamp: str, target: str):
S, T = list(stamp), list(target)
slen, tlen = len(stamp), len(target)
res = []
visited = [False] * (tlen - slen + 1)
stars = 0
def can_stamp(pos):
changed = False
for i in range(slen):
if T[pos + i] == '?':
continue
if T[pos + i] != S[i]:
return False
changed = True
return changed
def do_stamp(pos):
nonlocal stars
for i in range(slen):
if T[pos + i] != '?':
T[pos + i] = '?'
stars += 1
while stars < tlen:
stamped = False
for i in range(tlen - slen + 1):
if not visited[i] and can_stamp(i):
do_stamp(i)
visited[i] = True
res.append(i)
stamped = True
if not stamped:
return []
return res[::-1]
class Solution {
public:
vector<int> movesToStamp(string stamp, string target) {
int slen = stamp.size(), tlen = target.size();
vector<char> T(target.begin(), target.end());
vector<bool> visited(tlen - slen + 1, false);
vector<int> res;
int stars = 0;
auto can_stamp = [&](int pos) {
bool changed = false;
for (int i = 0; i < slen; ++i) {
if (T[pos + i] == '?') continue;
if (T[pos + i] != stamp[i]) return false;
changed = true;
}
return changed;
};
auto do_stamp = [&](int pos) {
for (int i = 0; i < slen; ++i) {
if (T[pos + i] != '?') {
T[pos + i] = '?';
++stars;
}
}
};
while (stars < tlen) {
bool stamped = false;
for (int i = 0; i <= tlen - slen; ++i) {
if (!visited[i] && can_stamp(i)) {
do_stamp(i);
visited[i] = true;
res.push_back(i);
stamped = true;
}
}
if (!stamped) return {};
}
reverse(res.begin(), res.end());
return res;
}
};
class Solution {
public int[] movesToStamp(String stamp, String target) {
char[] S = stamp.toCharArray();
char[] T = target.toCharArray();
int slen = S.length, tlen = T.length;
boolean[] visited = new boolean[tlen - slen + 1];
List<Integer> res = new ArrayList<>();
int stars = 0;
while (stars < tlen) {
boolean stamped = false;
for (int i = 0; i <= tlen - slen; i++) {
if (!visited[i] && canStamp(T, i, S)) {
stars += doStamp(T, i, S.length);
visited[i] = true;
res.add(i);
stamped = true;
}
}
if (!stamped) return new int[0];
}
int[] ans = new int[res.size()];
for (int i = 0; i < res.size(); i++) {
ans[i] = res.get(res.size() - 1 - i);
}
return ans;
}
private boolean canStamp(char[] T, int pos, char[] S) {
boolean changed = false;
for (int i = 0; i < S.length; i++) {
if (T[pos + i] == '?') continue;
if (T[pos + i] != S[i]) return false;
changed = true;
}
return changed;
}
private int doStamp(char[] T, int pos, int slen) {
int count = 0;
for (int i = 0; i < slen; i++) {
if (T[pos + i] != '?') {
T[pos + i] = '?';
count++;
}
}
return count;
}
}
var movesToStamp = function(stamp, target) {
const S = stamp.split(''), T = target.split('');
const slen = S.length, tlen = T.length;
const visited = new Array(tlen - slen + 1).fill(false);
const res = [];
let stars = 0;
function canStamp(pos) {
let changed = false;
for (let i = 0; i < slen; i++) {
if (T[pos + i] === '?') continue;
if (T[pos + i] !== S[i]) return false;
changed = true;
}
return changed;
}
function doStamp(pos) {
for (let i = 0; i < slen; i++) {
if (T[pos + i] !== '?') {
T[pos + i] = '?';
stars++;
}
}
}
while (stars < tlen) {
let stamped = false;
for (let i = 0; i <= tlen - slen; i++) {
if (!visited[i] && canStamp(i)) {
doStamp(i);
visited[i] = true;
res.push(i);
stamped = true;
}
}
if (!stamped) return [];
}
return res.reverse();
};