Taro Logo

Using a Robot to Print the Lexicographically Smallest String

Medium
Asked by:
Profile picture
Profile picture
Profile picture
108 views
Topics:
StringsStacksGreedy Algorithms

You are given a string s and a robot that currently holds an empty string t. Apply one of the following operations until s and t are both empty:

  • Remove the first character of a string s and give it to the robot. The robot will append this character to the string t.
  • Remove the last character of a string t and give it to the robot. The robot will write this character on paper.

Return the lexicographically smallest string that can be written on the paper.

Example 1:

Input: s = "zza"
Output: "azz"
Explanation: Let p denote the written string.
Initially p="", s="zza", t="".
Perform first operation three times p="", s="", t="zza".
Perform second operation three times p="azz", s="", t="".

Example 2:

Input: s = "bac"
Output: "abc"
Explanation: Let p denote the written string.
Perform first operation twice p="", s="c", t="ba". 
Perform second operation twice p="ab", s="c", t="". 
Perform first operation p="ab", s="", t="c". 
Perform second operation p="abc", s="", t="".

Example 3:

Input: s = "bdda"
Output: "addb"
Explanation: Let p denote the written string.
Initially p="", s="bdda", t="".
Perform first operation four times p="", s="", t="bdda".
Perform second operation four times p="addb", s="", t="".

Constraints:

  • 1 <= s.length <= 105
  • s consists of only English lowercase 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 possible characters that can appear in the input string, and is the input string guaranteed to be non-empty?
  2. If the robot has multiple options for its next move that lead to lexicographically equivalent results, how should it choose which path to take?
  3. What are the constraints on the size of the input string `s`? Specifically, what's the maximum possible length?
  4. If the robot reaches a state where it can no longer move (either no characters are left in `s` or `t`), what string should be returned? Should I return the string printed thus far?
  5. Is the input string only composed of lowercase English letters, or can it contain uppercase letters, numbers, or other special characters?

Brute Force Solution

Approach

The brute force approach involves exploring every possible sequence of actions the robot can take. This means trying out all combinations of moving characters and printing them, without any clever shortcuts. We evaluate each possible printed string to see which one comes earliest in the dictionary.

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

  1. Consider the input string and all possible actions the robot can take at each step: move a character to the robot's hand or print a character from the hand.
  2. Start with the robot's hand empty and the output string empty.
  3. Try every possible sequence of actions: move a character, then print; move, then move again; print, then move; print, then print (if the hand isn't empty), and so on.
  4. For each sequence of actions, keep track of the resulting printed string.
  5. Once a sequence of actions has processed all characters from the input string, store the printed string.
  6. After exploring all possible sequences of actions, you will have a collection of printed strings.
  7. Compare all the printed strings in the collection to find the one that comes earliest in dictionary order. This is the lexicographically smallest string.

Code Implementation

def find_lexicographically_smallest_string_brute_force(input_string):
    smallest_string_found = None

    def generate_strings(remaining_string, hand, current_string):
        nonlocal smallest_string_found

        if not remaining_string and not hand:
            # Base case: no more characters to process.
            if smallest_string_found is None or current_string < smallest_string_found:
                smallest_string_found = current_string
            return

        # Option 1: Move a character from the input string to the hand
        if remaining_string:
            first_character = remaining_string[0]
            rest_of_string = remaining_string[1:]
            generate_strings(rest_of_string, hand + first_character, current_string)

        # Option 2: Print a character from the hand
        if hand:
            #Printing allows us to build a new candidate
            generate_strings(remaining_string, hand[1:], current_string + hand[0])

    generate_strings(input_string, "", "")
    return smallest_string_found

Big(O) Analysis

Time Complexity
O(2^n)The brute force approach explores all possible combinations of moving characters to the robot's hand or printing from the hand. In the worst case, for each of the n characters in the input string, the robot can either move it to the hand or print a character from the hand (if available). This creates a binary decision tree where each node has two branches, leading to 2^n possible execution paths. Therefore, the time complexity grows exponentially with the input size n, resulting in O(2^n).
Space Complexity
O(2^N)The brute force approach explores all possible sequences of actions. Each action involves either moving a character to the robot's hand or printing a character, resulting in a binary decision tree. In the worst case, we need to explore all possible combinations, leading to the creation of up to 2^N possible printed strings, where N is the length of the input string. Storing these strings requires space proportional to the number of strings generated, thus O(2^N). The space to store these strings dominates the space complexity.

Optimal Solution

Approach

The goal is to find the best possible string in dictionary order using a robot that can move characters. The key is to greedily pick the smallest character available and move it to the output string whenever possible, prioritizing earlier occurrences of the smallest character.

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

  1. First, scan the entire input string and keep track of the smallest character seen so far.
  2. Also, track which characters are coming up in the string after each position.
  3. Now, go through the input string from left to right.
  4. If the current character is the smallest one we've seen so far, move it to the output.
  5. However, before moving a character, we must check if any smaller character is coming up later in the string.
  6. If a smaller character exists later, we skip moving the current character to the output because it's better to wait for the smaller character.
  7. Repeat this process until the input string is empty; this will create the lexicographically smallest output string.

Code Implementation

def robot_print_smallest_string(input_string):
    string_length = len(input_string)
    smallest_character = min(input_string)
    output_string = ""
    remaining_characters = list(input_string)

    for i in range(string_length):
        current_character = input_string[i]

        # Check if a smaller character exists later in the string.
        smaller_exists = False
        for j in range(i + 1, string_length):
            if input_string[j] < current_character:
                smaller_exists = True
                break

        # Append the character to output if no smaller char exists later.
        if not smaller_exists and current_character <= smallest_character:
            output_string += current_character
            remaining_characters.remove(current_character)
            input_string = "".join(remaining_characters)
            string_length = len(input_string)

            #Find new smallest character.
            if remaining_characters:
                smallest_character = min(remaining_characters)
            else:
                break

        else:
            remaining_characters.remove(current_character)
            input_string = "".join(remaining_characters)
            string_length = len(input_string)

    return output_string

Big(O) Analysis

Time Complexity
O(n²)The algorithm first scans the input string of length n to find the smallest character and track upcoming characters, which takes O(n) time. Then, it iterates through the string again (O(n)). In each iteration, it checks if a smaller character appears later in the string, requiring another scan of the remaining part of the string in the worst case (up to n operations). This nested checking results in a cost of approximately n * n/2, simplifying to O(n²). Therefore, the overall time complexity is dominated by the nested loop operation, leading to O(n²).
Space Complexity
O(N)The algorithm requires storing information about which characters are coming up later in the string after each position. This can be implemented using an auxiliary array or hash map of size N, where N is the length of the input string, to track the occurrences of each character at different positions. The algorithm also tracks the smallest character seen so far, which uses constant space, but the dominant space usage is the tracking of future character occurrences. Therefore, the overall auxiliary space complexity is O(N).

Edge Cases

Null or empty input string
How to Handle:
Return an empty string or null; the specific action should be clarified during the interview.
Input string with only one character
How to Handle:
The robot has nothing to reverse, so the lexicographically smallest string is the input string itself; return it.
Input string is already lexicographically smallest
How to Handle:
The algorithm should still execute correctly, potentially reversing nothing, and return the original string.
Input string with all identical characters (e.g., 'aaaa')
How to Handle:
The algorithm should handle this gracefully, potentially reversing only at the end, or doing nothing if already optimal.
Input string with characters in strictly decreasing order (e.g., 'zyxw')
How to Handle:
Reversing from beginning to end may result in the lexicographically smallest string.
Maximum string length (performance considerations)
How to Handle:
Ensure the chosen algorithm has optimal time complexity, preferably O(n) or O(n log n) depending on approach and avoids unnecessary string copying for very large strings.
Memory constraints for very large input string
How to Handle:
If memory is extremely limited, consider in-place string manipulation techniques or streaming algorithms if possible, clarifying memory constraints upfront.
String contains non-alphabetic characters (e.g., numbers, symbols)
How to Handle:
Clarify with the interviewer if only lowercase alphabets will be present; otherwise, ensure the comparison considers all possible characters in the defined character set (e.g., ASCII, UTF-8).