936. Stamping The Sequence

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zjucor/article/details/83715186

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 turns.

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 moves.  Any answers specifying more than this number of moves will not be accepted.

 

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. 1 <= stamp.length <= target.length <= 1000
  2. stamp and target 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 ith window, a subarray of target of length stamp.length that starts at i. Each move at position i is possible if the ith 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 stamptarget.

  • Space Complexity: O(N(N-M)).

一开始怎么也看不懂,debug几个case就能容易看懂思路了。对于标准答案,自己是想到了要预处理stamp和target,也想到了用event-driven的思想优化,就是组合不起来。

不过把一对多转化为多对一的思想还是值得借鉴的。

猜你喜欢

转载自blog.csdn.net/zjucor/article/details/83715186