Coding Interview Patterns Module 20 – Graph patterns: traversal
Reconstruct an itinerary with Eulerian paths
Rebuild a trip that used every ticket exactly once, alphabetically first when several exist, with the brute force over every order and the signal to notice.
What you will learn
- Check the in and out counts that decide whether a path using every edge once exists, and where it ends
- Build the path with Hierholzer's algorithm and an explicit stack
- Find the alphabetically first itinerary by taking each station's tickets in sorted order
- Prove that the result uses every edge once and that no earlier itinerary exists
- Test an itinerary builder against a backtracking search on every small ticket set
Before you start
What you’ll learn · Before you start
What you will learn
- Check the in and out counts that decide whether a path using every edge once exists, and where it ends
- Build the path with Hierholzer's algorithm and an explicit stack
- Find the alphabetically first itinerary by taking each station's tickets in sorted order
- Prove that the result uses every edge once and that no earlier itinerary exists
- Test an itinerary builder against a backtracking search on every small ticket set
Before you start
On this page
To reconstruct an itinerary from a pile of used tickets is to find an Eulerian path: a route through a graph that uses every edge exactly once. Stations are the nodes and tickets the directed edges. Such a route exists only when every station has as many tickets in as out, except that the start may have one more out and the end one more in. Hierholzer’s algorithm then builds the route in linear time, and taking each station’s alphabetically smallest ticket first makes it the alphabetically first itinerary.
The problem: a trip rebuilt from its ticket stubs
A traveller kept the stub of every ticket of a rail trip. Each stub names the station the leg left from and the station it reached, as three-letter codes. The trip began at a known station and used every ticket exactly once. Rebuild the stations in order. When several orders are possible, return the one that comes first alphabetically, comparing station by station. A trip always exists, and the same ticket may appear twice. Limits: 1 to 10,000 tickets.
AMB → BRK, BRK → CAS, CAS → AMB, AMB → DALfrom AMB givesAMB BRK CAS AMB DAL.AMB → BRK, AMB → CAS, CAS → AMBfrom AMB givesAMB CAS AMB BRK, although BRK comes before CAS.AMB → BRK, BRK → AMB, AMB → BRKfrom AMB givesAMB BRK AMB BRK: the repeated ticket is used twice.
The first two examples as graphs of stations and tickets
Text description of the diagram
Each box is a station and each arrow a ticket, from the station it starts at to the station it goes to. The number on an arrow is that ticket's place in the answer.
The upper panel shows example 1: arrows from AMB to BRK (1), BRK to CAS (2), CAS back to AMB (3) and AMB to DAL (4). Following the numbers gives the trip AMB, BRK, CAS, AMB, DAL, which uses every ticket once.
The lower panel shows example 2: arrows from AMB to CAS (1), CAS back to AMB (2), and a dashed arrow from AMB to BRK labelled "3: last". BRK is labelled as the end of the trip. The ticket to BRK is the alphabetically smallest one out of AMB, but BRK has no ticket out, so taking it first would end the trip with two tickets unused. The trip is AMB, CAS, AMB, BRK.
The brute force, and why it fails at these sizes
Try every order of the tickets, keep the orders in which each leg starts where the one before it ended, and return the alphabetically first trip among them.
"""The brute force: every order of the tickets, keeping the alphabetically first one that makes a trip. The work is
counted, not timed, so every computer prints the same."""
from itertools import permutations
def first_trip(tickets, start):
tried, best = 0, None
for order in permutations(tickets):
tried += 1
here, stations = start, [start]
for a, b in order:
if a != here:
break
stations.append(b)
here = b
else: # every ticket was used, each leg starting where the last one ended
best = stations if best is None else min(best, stations)
return best, tried
print("example 1:", " ".join(first_trip([("AMB", "BRK"), ("BRK", "CAS"), ("CAS", "AMB"), ("AMB", "DAL")], "AMB")[0]))
print("example 2:", " ".join(first_trip([("AMB", "BRK"), ("AMB", "CAS"), ("CAS", "AMB")], "AMB")[0]))
print("example 3:", " ".join(first_trip([("AMB", "BRK"), ("BRK", "AMB"), ("AMB", "BRK")], "AMB")[0]))
print("tickets orders tried")
for n in (6, 7, 8):
loop = [(f"S{i}", f"S{(i + 1) % n}") for i in range(n)] # one round trip through n stations
print(f"{n:>7} {first_trip(loop, 'S0')[1]:>12,}")
print("one more ticket multiplies the orders: 10 give 3,628,800, 20 about 2.4 x 10^18, 10,000 a number of 35,660 digits") Output
example 1: AMB BRK CAS AMB DAL
example 2: AMB CAS AMB BRK
example 3: AMB BRK AMB BRK
tickets orders tried
6 720
7 5,040
8 40,320
one more ticket multiplies the orders: 10 give 3,628,800, 20 about 2.4 x 10^18, 10,000 a number of 35,660 digits
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 brute_force.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
It is right on the examples, but n tickets have n! orders: 40,320 for 8 tickets, 3,628,800 for 10, and for 10,000
tickets a number with 35,660 digits. Most of those orders break at the first leg, and still the count alone rules the
method out.
What to notice
- It is a graph problem. A trip that uses every ticket once is a path that uses every edge once.
- Counting decides where it ends. A station the trip passes through is left as often as it is entered, so only the start can have an extra ticket out and only the end an extra ticket in.
- The smallest ticket is not always the next leg. In example 2, AMB → BRK leads to the station where the trip must end, so it has to be used last.
Four techniques follow from these signals, and two variants use them:
- Degree counting compares tickets in and out of every station, to check that a trip exists and where it ends.
- Hierholzer’s algorithm walks along unused tickets and writes a station down once it has no ticket left.
- Sorted ticket lists make the trip it builds the alphabetically first one.
- An explicit stack replaces the recursion, so 10,000 tickets in a row need no deep call stack.
- The variants walk two-way streets with numbered edges and build the shortest string that holds every lock code.
When a trip exists: tickets in and out Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- Hierholzer’s algorithm with a stack, step by step
- Why it uses every ticket exactly once
- Why smallest first gives the alphabetically first trip
- A dry run on the dead end
- The code in five languages
- How much work it does
- Edge cases and common bugs
- Variants: streets both ways and a keypad lock
- Follow-up questions
- Exercise: rebuild the alphabetically first trip
- Rebuild the alphabetically first trip from ticket stubs
- A practice set with sample answers
- Check yourself
- Interview questions
- What must the tickets in and out of each station look like for a trip that uses every ticket once to exist, and where must that trip start?
- Your itinerary code always takes the alphabetically smallest unused ticket out of the current station. It passes the samples but returns a trip that leaves tickets unused on a hidden test. Give an input that breaks it, and fix the code without losing the alphabetical order.
- A street-cleaning vehicle must drive along every one of a town's 3,000 two-way streets at least once and return to its depot. When can it drive each street exactly once, and what do you compute when it cannot?
- A sync service receives 1,000,000 overlapping 3-character chunks of a log, where each chunk shares its last 2 characters with the first 2 of the next one. Rebuild a log that uses every chunk exactly once. How do you model it, how fast is it, and when is the answer ambiguous?
- Key takeaways
5 runnable examples (C++, Go, Java, JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 24 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
References
- Euler cycle (Dictionary of Algorithms and Data Structures) (National Institute of Standards and Technology (NIST))
- Ueber die Möglichkeit, einen Linienzug ohne Wiederholung und ohne Unterbrechung zu umfahren (C. Hierholzer and C. Wiener, Mathematische Annalen 6, pages 30–32) (Springer)
- Solutio problematis ad geometriam situs pertinentis (L. Euler, E53, Commentarii academiae scientiarum Petropolitanae 8, pages 128–140) (The Euler Archive, University of the Pacific)
- A combinatorial problem (N. G. de Bruijn, Proceedings of the KNAW 49, pages 758–764) (Royal Netherlands Academy of Arts and Sciences (KNAW))
- collections.defaultdict (The Python Standard Library) (Python Software Foundation)
- heapq: heap queue algorithm (The Python Standard Library) (Python Software Foundation)
- Sorting Techniques (Python HOWTOs) (Python Software Foundation)
- sys.getrecursionlimit() (The Python Standard Library) (Python Software Foundation)
- Math.imul() (MDN Web Docs)
- List (Java SE 25 and JDK 25 API): removeLast() and getFirst() (Oracle)
- Package slices: SortFunc (The Go Authors)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress