Cracking the Safe looks like a brute-force nightmare — try every password until one works — but it is secretly a graph problem. The trick is to see that overlapping passwords share digits, and to find the one string that packs all of them together: a De Bruijn sequence, built by an Eulerian walk.
Problem. A safe opens when the last n digits typed match the secret. There are k possible
digits (0 to k-1). Return any shortest string that is guaranteed to open the safe — it must
contain every possible length-n password as a substring.
Example: n = 2, k = 2 → the four passwords are 00, 01, 10, 11. A shortest answer is
"00110" (length 5): it contains 00, 01, 11, and 10 as you slide a window across it.
The slow way first
The naive idea: list all k^n passwords and concatenate them. For n = 2, k = 2 that is
00 + 01 + 10 + 11 = "00011011", length 8. It works, but it wastes digits — it never reuses
the overlap between one password and the next.
The question to ask: can the end of one password be the start of the next? The password 00 ends in
0, which is exactly where 01 starts. If we overlap them, 001 already contains both 00 and 01
using only 3 digits instead of 4. Chasing that overlap all the way gives the optimal length
k^n + (n − 1).
The idea: walk a prefix graph
Make every (n − 1)-digit prefix a node. For n = 2 the prefixes are single digits: "0" and
"1". Appending a digit d to a prefix is a directed edge that lands on the new prefix (drop the
oldest digit, keep the last n − 1). Each edge corresponds to exactly one length-n substring.
If we can walk a path that crosses every edge exactly once (an Eulerian trail), the digits we append along the way spell a string containing every password. A greedy Hierholzer DFS — always take any unused edge, backtrack when stuck — finds it.
The key insight: covering every length-n substring is the same as using every edge of this prefix
graph, and a De Bruijn sequence is exactly its Eulerian trail.
Walk through it
Step through the animation. We seed the string with "0" and stand on prefix "0". From each prefix we
greedily try the smallest digit whose substring has not been used: 00, then 01, then 11, then 10.
Each accepted digit gets appended to the password. After four edges every substring is covered and the
string reads "00110".
Pseudocode
start = "0" repeated (n - 1) times # any (n-1)-length prefix
seen = empty set # substrings already used (= edges taken)
password = empty list
dfs(node): # node is the current prefix
for d in 0 .. k-1: # try each digit, smallest first
substring = node + d
if substring not in seen:
mark substring as seen
dfs(last n-1 chars of substring) # walk the edge
append d to password # record on the way back
dfs(start)
answer = password joined together + startThe Python solution
def crack_safe(n, k):
start = "0" * (n - 1)
seen = set()
password = []
def dfs(node):
for d in map(str, range(k)):
nxt = node + d
if nxt not in seen:
seen.add(nxt)
dfs(nxt[1:])
password.append(d)
dfs(start)
return "".join(password) + startstartis the seed prefix ofn − 1zeros; forn = 2that is just"0".seenis the set of length-nsubstrings we have already used — equivalently, the edges crossed.dfs(node)stands on a prefix and tries each digitdin order (range(k)keeps it greedy/smallest).nxt = node + dis the substring this edge represents; if it is new, we take the edge: mark it seen and recurse intonxt[1:], the new(n − 1)-prefix.password.append(d)runs on the way back up (post-order). This Hierholzer detail guarantees the appended digits compose a valid Eulerian trail even when the DFS has to backtrack.- The final
+ startre-attaches the seed prefix so the very first password also appears.
Complexity
| Case | Time | Notes |
|---|---|---|
| Concatenate every password | O(n · k^n) (moderate) | wastes the overlap |
| Eulerian walk (this solution) | O(k^n) (moderate) | one edge per substring |
O(k^n) (moderate)There are k^n substrings, so there are k^n edges and the DFS touches each once. The output string has
length k^n + (n − 1) — provably the shortest possible. The seen set also holds k^n entries, giving
O(k^n) space.
When this pattern shows up
When a problem asks for the shortest string containing every length-n combination (or every transition), think De Bruijn sequence and model it as an Eulerian trail over a prefix graph. The same Hierholzer DFS — take any unused edge, append on the way back — reconstructs the route.
Append the digit in post-order (after the recursive call), not before. Appending before the recursion produces a string that breaks whenever the DFS backtracks, because the trailing digits no longer form a valid continuation of the walk.
Practice
For n = 2, k = 2, after the walk has covered 00, 01, and 11, you are standing on prefix 1. Which digit do you append next and what substring does it cover?
1. What do the nodes of the graph represent?
2. Covering every length-n password is equivalent to what graph task?
3. What is the length of the shortest answer string?
4. Why is the digit appended after the recursive call rather than before?