936. Stamping The Sequence
You want to form a target
string of lowercase letters.
At the beginning, your sequence is target.length
'?'
marks. You also have a stamp
of lowercase letters.
On each turn, you may place the stamp over the sequence, and replace every letter in the sequence with the corresponding letter from the stamp. You can make up to 10 * target.length
For example, if the initial sequence is "?????", and your stamp is "abc"
, then you may make "abc??", "?abc?", "??abc" in the first turn. (Note that the stamp must be fully contained in the boundaries of the sequence in order to stamp.)
If the sequence is possible to stamp, then return an array of the index of the left-most letter being stamped at each turn. If the sequence is not possible to stamp, return an empty array.
For example, if the sequence is "ababc", and the stamp is "abc"
, then we could return the answer [0, 2]
, corresponding to the moves "?????" -> "abc??" -> "ababc".
Also, if the sequence is possible to stamp, it is guaranteed it is possible to stamp within 10 * target.length
Example 1:
Input: stamp = "abc", target = "ababc"
Output: [0,2]
([1,0,2] would also be accepted as an answer, as well as some other answers.)
Example 2:
Input: stamp = "abca", target = "aabcaca"
Output: [3,0,1]
Note:
1 <= stamp.length <= target.length <= 1000
stamp
andtarget
only contain lowercase letters.
思路:
1. 原問題是'????'到'abcs'這樣的問題,在替換的時候就要考慮:不能影響之前已經替換好的部分。這個其實還不是很好解決的。如果反過來就很簡單了,及從'abcs'到'????',因為這時候的目標變成了唯一的'?',相同位置覆蓋,結果也是一樣的,基於這個想法,可以寫一個naive的版本(https://leetcode.com/problems/stamping-the-sequence/discuss/189258/C%2B%2B-Reverse-Operation-30-ms-better-than-DFS)
class Solution:
def movesToStamp(self, stamp, target):
"""
:type stamp: str
:type target: str
:rtype: List[int]
"""
n,m=len(target),len(stamp)
s=[target]
res=[]
def remove():
for i in range(n):
ok,j,ti=False,0,i
while j<m and ti<n and (s[0][ti]=='?' or s[0][ti]==stamp[j]):
if s[0][ti]==stamp[j]: ok=True
j+=1
ti+=1
if ok and j==m:
s[0]=s[0][:i]+'?'*m+s[0][i+m:]
return i
return -1
while s[0]!='?'*n:
t = remove()
if t==-1: return []
res.append(t)
return res[::-1]
可惜會TLE,複雜度NNM
2. remove()函式有很多的重複計算,我們並不需要每次從頭遍歷,可以用event-driven的思想,只有當附近的字元變化時才有可能使得當前位置的字元變得可以匹配(需要先預處理stamp和target,https://leetcode.com/articles/stamping-the-sequence/)
因為這個連結裡面的程式碼說的很詳細,所以就直接copy過來了
import collections
class Solution(object):
def movesToStamp(self, stamp, target):
M, N = len(stamp), len(target)
queue = collections.deque()
done = [False] * N
ans = []
A = []
for i in range(N - M + 1):
# For each window [i, i+M),
# A[i] will contain info on what needs to change
# before we can reverse stamp at i.
made, todo = set(), set()
for j, c in enumerate(stamp):
a = target[i+j]
if a == c:
made.add(i+j)
else:
todo.add(i+j)
A.append((made, todo))
# If we can reverse stamp at i immediately,
# enqueue letters from this window.
if not todo:
ans.append(i)
for j in range(i, i + len(stamp)):
if not done[j]:
queue.append(j)
done[j] = True
# For each enqueued letter,
while queue:
i = queue.popleft()
# For each window that is potentially affected,
# j: start of window
for j in range(max(0, i-M+1), min(N-M, i)+1):
if i in A[j][1]: # This window is affected
A[j][1].discard(i) # Remove it from todo list of this window
if not A[j][1]: # Todo list of this window is empty
ans.append(j)
for m in A[j][0]: # For each letter to potentially enqueue,
if not done[m]:
queue.append(m)
done[m] = True
return ans[::-1] if all(done) else []
Intuition
Imagine we stamped the sequence with moves m_1, m_2, \cdotsm1,m2,⋯. Now, from the final position target
, we will make those moves in reverse order.
Let's call the i
th window, a subarray of target
of length stamp.length
that starts at i
. Each move at position i
is possible if the i
th window matches the stamp. After, every character in the window becomes a wildcard that can match any character in the stamp.
For example, say we have stamp = "abca"
and target = "aabcaca"
. Working backwards, we will reverse stamp at window 1
to get "a????ca"
, then reverse stamp at window 3
to get "a??????"
, and finally reverse stamp at position 0
to get "???????"
.
Algorithm
Let's keep track of every window. We want to know how many cells initially match the stamp (our "made
" list), and which ones don't (our "todo"
list). Any windows that are ready (ie. have no todo list), get enqueued.
Specifically, we enqueue the positions of each character. (To save time, we enqueue by character, not by window.) This represents that the character is ready to turn into a "?"
in our working target
string.
Now, how to process characters in our queue? For each character, let's look at all the windows that intersect it, and update their todo lists. If any todo lists become empty in this manner (window.todo is empty)
, then we enqueue the characters in window.made
that we haven't processed yet.
Complexity Analysis
-
Time Complexity: O(N(N-M)), where M,N are the lengths of
stamp
,target
. -
Space Complexity: O(N(N-M)).
一開始怎麼也看不懂,debug幾個case就能容易看懂思路了。對於標準答案,自己是想到了要預處理stamp和target,也想到了用event-driven的思想優化,就是組合不起來。
不過把一對多轉化為多對一的思想還是值得借鑑的。