Taro Logo

Cracking the Safe

Hard
Asked by:
Profile picture
20 views
Topics:
Graphs

There is a safe protected by a password. The password is a sequence of n digits where each digit can be in the range [0, k - 1].

The safe has a peculiar way of checking the password. When you enter in a sequence, it checks the most recent n digits that were entered each time you type a digit.

  • For example, the correct password is "345" and you enter in "012345":
    • After typing 0, the most recent 3 digits is "0", which is incorrect.
    • After typing 1, the most recent 3 digits is "01", which is incorrect.
    • After typing 2, the most recent 3 digits is "012", which is incorrect.
    • After typing 3, the most recent 3 digits is "123", which is incorrect.
    • After typing 4, the most recent 3 digits is "234", which is incorrect.
    • After typing 5, the most recent 3 digits is "345", which is correct and the safe unlocks.

Return any string of minimum length that will unlock the safe at some point of entering it.

Example 1:

Input: n = 1, k = 2
Output: "10"
Explanation: The password is a single digit, so enter each digit. "01" would also unlock the safe.

Example 2:

Input: n = 2, k = 2
Output: "01100"
Explanation: For each possible password:
- "00" is typed in starting from the 4th digit.
- "01" is typed in starting from the 1st digit.
- "10" is typed in starting from the 3rd digit.
- "11" is typed in starting from the 2nd digit.
Thus "01100" will unlock the safe. "10011", and "11001" would also unlock the safe.

Constraints:

  • 1 <= n <= 4
  • 1 <= k <= 10
  • 1 <= kn <= 4096

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 is the character set used for the password? Is it only lowercase letters, or are uppercase letters, numbers, and special characters also possible?
  2. What is the maximum length of the password?
  3. Are there any restrictions on the composition of the password, such as requiring at least one number or one special character?
  4. If there are multiple valid shortest password sequences, is any valid shortest sequence acceptable?
  5. How should I handle the case where the given 'n' (total possible combinations) is exceptionally large, potentially exceeding memory limits?

Brute Force Solution

Approach

The brute force approach to cracking the safe involves trying every possible combination of numbers until we find the right one. It's like trying every single possible password. We continue guessing until we hit the jackpot, regardless of how long it takes.

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

  1. Start with the simplest combination, like all zeros (0000 if the code is four digits).
  2. Try the next combination, incrementing the last digit by one (0001).
  3. Continue incrementing the last digit until you've tried all possibilities for that digit (0002, 0003, ..., 0009).
  4. Once the last digit has been exhausted, reset it to zero and increment the digit before it (0010).
  5. Repeat the process of incrementing digits from right to left, trying every possible combination.
  6. After each guess, check if the combination opens the safe. If it does, you're done!
  7. If you reach the highest possible combination (9999 for a four-digit code) without opening the safe, then either the safe code is wrong or there is a problem with the safe.

Code Implementation

def crack_the_safe(number_of_digits, correct_safe_code):
    maximum_possible_combination = int('9' * number_of_digits)
    current_combination = 0

    while current_combination <= maximum_possible_combination:
        # Convert number to string with leading zeros for consistent length
        current_combination_string = str(current_combination).zfill(number_of_digits)

        # Check if the current combination opens the safe
        if current_combination_string == correct_safe_code:
            return current_combination_string

        current_combination += 1

    # Exhausted all combinations; safe code might be incorrect
    return None

Big(O) Analysis

Time Complexity
O(10^n)The brute force approach tries every possible combination of digits. Let n be the number of digits in the combination. Each digit can be any number from 0 to 9, meaning there are 10 possibilities for each digit. Since we try all possible combinations, the total number of attempts is 10 multiplied by itself n times, which is 10^n. Thus, the time complexity is O(10^n).
Space Complexity
O(1)The brute force approach described only involves trying different combinations without storing any of the tried combinations. The algorithm generates combinations one at a time and checks it against the safe's lock. Thus, no auxiliary data structures that grow with the size of the possible combinations are used, resulting in constant auxiliary space, O(1).

Optimal Solution

Approach

The key to cracking the safe efficiently lies in recognizing overlapping patterns. We can construct the password by strategically reusing previously entered sequences, avoiding brute-force attempts.

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

  1. Start with a simple sequence of digits. For example, use all zeros for the length needed.
  2. Then, keep appending new digits to the existing sequence. The trick is to make sure the last portion of your sequence (the same length as the combination) overlaps with a previously entered sequence as much as possible.
  3. If the new sequence formed has not been tried before, add the new digit. If it has been tried already, try a different new digit.
  4. Repeat this process of appending and checking for overlaps until you've tested all possible digit combinations.
  5. The final sequence you've created will contain all possible combinations of the required length, guaranteeing that you'll open the safe.

Code Implementation

def crack_the_safe(number_of_digits, combination_length):
    starting_sequence = '0' * combination_length
    safe_combination = starting_sequence
    all_combinations = set()
    all_combinations.add(starting_sequence)

    # Generate all possible combinations
    for _ in range(number_of_digits ** combination_length):
        for digit in range(number_of_digits):
            new_combination = safe_combination[-(combination_length - 1):] + str(digit)

            # Ensure to not repeat combination.
            if new_combination not in all_combinations:
                safe_combination += str(digit)
                all_combinations.add(new_combination)
                break

    return safe_combination

Big(O) Analysis

Time Complexity
O(k^n)The algorithm aims to generate a de Bruijn sequence. The length of the final sequence will be k^n, where k is the number of possible digits (e.g., 10 for digits 0-9) and n is the length of the combination. Appending a new digit and checking if the resulting n-digit combination has already been tried requires, in the worst case, iterating through all previously generated combinations. Since there are k^n possible combinations, the overall time complexity is approximately proportional to the number of combinations generated multiplied by the cost of checking each new combination, leading to O(k^n).
Space Complexity
O(K^N)The algorithm needs to keep track of all tried combinations to avoid repetition. Since each combination is of length N and each digit can be one of K possible values (where K is the number of possible digits, commonly 10), there are K^N possible combinations. These combinations are stored in a set or similar data structure to efficiently check for duplicates. Therefore, the auxiliary space required grows proportionally to the number of possible combinations, resulting in a space complexity of O(K^N).

Edge Cases

n is 0
How to Handle:
Return an empty string immediately as there's no possible password.
k is 1
How to Handle:
The shortest possible string is '0'*n + '0'*(n-1), which covers all possible passwords.
n is 1
How to Handle:
The shortest possible string is '012...k-1', which covers all possible passwords.
n is very large leading to large memory usage.
How to Handle:
The algorithm should still work in principle but memory limits could be exceeded depending on system constraints.
n or k is negative
How to Handle:
Throw an IllegalArgumentException, as negative values are invalid.
n and k are both 1
How to Handle:
The solution should return '01' which covers all possible passwords.
When k is very large, ensuring all combinations are covered
How to Handle:
The algorithm's design ensures all combinations are covered irrespective of how big k is, by traversing every possible node in the De Bruijn graph.
k is a very large number close to the maximum integer value
How to Handle:
Avoid integer overflow during the generation of the password, cast k to long if neccessary.