Taro Logo

Stamping The Sequence

Hard
Asked by:
Profile picture
17 views
Topics:
ArraysTwo PointersGreedy AlgorithmsStrings

You are given two strings stamp and target. Initially, there is a string s of length target.length with all s[i] == '?'.

In one turn, you can place stamp over s and replace every letter in the s with the corresponding letter from stamp.

  • For example, if stamp = "abc" and target = "abcba", then s is "?????" initially. In one turn you can:
    • place stamp at index 0 of s to obtain "abc??",
    • place stamp at index 1 of s to obtain "?abc?", or
    • place stamp at index 2 of s to obtain "??abc".
    Note that stamp must be fully contained in the boundaries of s in order to stamp (i.e., you cannot place stamp at index 3 of s).

We want to convert s to target using at most 10 * target.length turns.

Return an array of the index of the left-most letter being stamped at each turn. If we cannot obtain target from s within 10 * target.length turns, return an empty array.

Example 1:

Input: stamp = "abc", target = "ababc"
Output: [0,2]
Explanation: Initially s = "?????".
- Place stamp at index 0 to get "abc??".
- Place stamp at index 2 to get "ababc".
[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]
Explanation: Initially s = "???????".
- Place stamp at index 3 to get "???abca".
- Place stamp at index 0 to get "abcabca".
- Place stamp at index 1 to get "aabcaca".

Constraints:

  • 1 <= stamp.length <= target.length <= 1000
  • stamp and target consist of lowercase English letters.

Solution


Clarifying Questions

When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:

  1. What are the maximum lengths of the `stamp` and `target` strings?
  2. Can the `stamp` or `target` strings be empty or null?
  3. If multiple valid stamping orders exist, is any order acceptable, or is there a specific order that should be returned?
  4. If it's impossible to fully stamp the target, should I return an empty array, or is there a specific error code or exception to raise?
  5. Do the `stamp` and `target` strings consist of only lowercase English letters, or can they contain other characters (e.g., uppercase, numbers, special characters)?

Brute Force Solution

Approach

The brute force strategy involves trying all possible placements of the stamp on the target sequence. For each potential placement, we check if stamping at that location results in a match with part of the target. We repeat this process for every possible starting point and collect the successful stamp placements.

Here's how the algorithm would work step-by-step:

  1. Imagine the stamp is like a small template that you can lay down on the target sequence.
  2. Start by placing the stamp at the very beginning of the target.
  3. Check if the stamp perfectly matches the corresponding section of the target sequence. If it does, note down this placement.
  4. Move the stamp one position to the right and repeat the matching check. Again, note down the placement if it matches.
  5. Keep sliding the stamp one position at a time across the target sequence, checking for a match at each location.
  6. Once you reach a point where placing the stamp would extend beyond the end of the target sequence, stop.
  7. Now, look at all the stamp placements you noted down. Figure out if using these placements in some order can completely transform the target sequence from a set of question marks to the final target.

Code Implementation

def stamping_the_sequence_brute_force(stamp, target):    stamp_length = len(stamp)
    target_length = len(target)
    possible_placements = []

    # Iterate through all possible starting positions of the stamp
    for start_position in range(target_length - stamp_length + 1):
        match = True
        for i in range(stamp_length):
            if target[start_position + i] != '?' and target[start_position + i] != stamp[i]:
                match = False
                break

        # Add the starting position if the stamp matches
        if match:
            possible_placements.append(start_position)

    # Check if the possible placements can transform the target
    for i in range(1 << len(possible_placements)):
        temp_target = ['?'] * target_length
        valid = True
        placements_used = []

        # Iterate through each possible placement and determine which ones to use
        for j in range(len(possible_placements)):
            if (i >> j) & 1:
                placements_used.append(possible_placements[j])

        # Apply the chosen stamps to the temporary target sequence
        for placement in placements_used:
            for k in range(stamp_length):
                temp_target[placement + k] = stamp[k]

        # Verify if the temporary target is the same as the original target
        for index in range(target_length):
            if temp_target[index] != target[index]:
                valid = False
                break

        # If temp target is equal to the target return the placements
        if valid:
            return placements_used

    return []

Big(O) Analysis

Time Complexity
O(m*n*(m+n))The brute force approach iterates through each possible starting position in the target sequence (length n) for placing the stamp (length m). For each of these n positions, it compares the stamp with the corresponding portion of the target, taking O(m) time. Furthermore, the problem requires a potentially exhaustive check to see if a sequence of stamp operations could arrive at the target, the worst-case is each stamp position is checked against all other stamp positions or against n target positions needing a factor of O(m+n). Thus, the total time complexity is O(m*n*(m+n)).
Space Complexity
O(N)The brute force strategy, as described, potentially stores all successful stamp placements. In the worst case, we might note a placement for almost every starting position on the target sequence. This means we are storing an array or list of stamp placements, and in the worst case, the number of placements could be related to the length of the target sequence. Therefore, auxiliary space scales linearly with the length of the target sequence which we can denote by N.

Optimal Solution

Approach

The goal is to work backwards. We repeatedly try to perfectly match the stamp onto the target, and if we do, we 'erase' the stamped section and count that as a move. We continue until the target is entirely stamped over.

Here's how the algorithm would work step-by-step:

  1. Start by looking for exact matches of the stamp within the target.
  2. If you find a match, 'erase' that section of the target (think of replacing the matched characters with a wildcard character that represents anything). Keep track of how many stamps you've used.
  3. Repeat the process of finding exact matches and erasing, prioritizing areas that were untouched previously. This ensures you are making progress towards stamping the whole target.
  4. Continue until the entire target is covered with 'erased' characters. If you can't cover the entire target, it's impossible to stamp the sequence.
  5. Finally, reverse the order in which you stamped. This gives you the sequence of stamps that produces the target string. If you could erase the target successfully, but some stamp order is invalid - then it is also an impossible operation.
  6. The steps are ordered such that we are first stamping later onto previous stamps.

Code Implementation

def movesToStamp(stamp, target):
    stamp_length = len(stamp)
    target_length = len(target)
    stamped = ['?'] * target_length
    stamped_string = ''.join(stamped)
    result = []
    visited = [False] * target_length

    while ''.join(stamped) != target:
        found = False
        for i in range(target_length - stamp_length + 1):
            if not visited[i] and canStamp(stamp, target, i):
                result.append(i)
                visited[i] = True
                replaceWithQuestionMarks(target, i, stamp_length, stamped)
                found = True

        if not found:
            return []

    result.reverse()
    return result

def canStamp(stamp, target, position):
    for i in range(len(stamp)):
        if target[position + i] != stamp[i] and target[position + i] != '?':
            return False
    return True

def replaceWithQuestionMarks(target, position, stamp_length, stamped):
    # Replace stamped section with question marks.
    for i in range(stamp_length):
        stamped[position + i] = '?'

Big(O) Analysis

Time Complexity
O(m*n*(m-n))The solution iterates, attempting to match the stamp (length m) against the target (length n) in each iteration. In the worst case, we scan the target for potential stamp matches, taking O(n) time per potential match. Matching itself takes O(m) in the worst case. If a match is found, we iterate through the stamp to erase it from the target, which is O(m). We repeat until either the entire target is stamped or no more matches are possible. The number of these major iterations is bound by (m-n), as it’s the max number of stamps needed to perfectly cover. Thus, the time complexity is approximately O(m*n*(m-n)).
Space Complexity
O(N)The algorithm uses a list to store the stamping order, which can have at most target.length elements, contributing O(N) space where N is the length of the target string. Additionally, the replace operation effectively modifies the target string, however, this can be considered in-place depending on the implementation and does not constitute auxiliary space. Therefore the dominant auxiliary space is from storing the stamping order, resulting in O(N) space complexity.

Edge Cases

Empty stamp string
How to Handle:
If the stamp is empty, the target can only be stamped if it is also empty, otherwise return an empty list.
Empty target string
How to Handle:
If the target is empty, the stamp must also be empty; return an empty list if the stamp is not empty.
Stamp longer than target
How to Handle:
If the stamp is longer than the target, it is impossible to stamp; return an empty list.
Stamp and target are identical
How to Handle:
Return a list containing index 0 representing the single stamp location.
Target cannot be stamped at all
How to Handle:
The algorithm will exhaust all possible stamp locations and return an empty list if the target remains unstamped.
Stamp consists of a single repeated character, and target consists of only this character, but target length is not a multiple of stamp length.
How to Handle:
Even if the stamp's characters are present in the target, ensure all positions can be covered.
Large stamp and target sizes can lead to performance issues with naive implementations.
How to Handle:
Employ a more efficient algorithm, such as a queue-based or greedy approach, to handle large inputs within the time constraint.
Multiple valid stamping orders exist
How to Handle:
Return any valid order that successfully stamps the target; the problem statement allows for multiple solutions.