149. Tower of Hanoi
Problem
Generate the legal moves for n disks in the Tower of Hanoi, moving the stack from peg A to peg C using peg B. Return a list of (source,target) moves. n is nonnegative; zero disks requires no moves. Only one top disk may move at a time, never onto a smaller disk.
Examples
Example 1
hanoi(3)
=> [('A', 'C'), ('A', 'B'), ('C', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('A', 'C')]Seven legal moves transfer three disks.
Example 2
hanoi(1)
=> [('A', 'C')]The largest disk is also the only disk.
Example 3
hanoi(0)
=> []There is no disk to move.
Reveal the worked solution and reasoning
Why this mechanism works
The largest disk cannot move until the n-1 smaller disks have been moved aside. Transfer them to the spare peg, move the largest disk to its destination, then transfer the smaller stack onto it. Each child call has the same contract with renamed peg roles.
State and boundaries
transfer(count,source,target,spare) appends moves that relocate exactly that legal stack while preserving disk order.
Protocol
- Return immediately for zero disks.
- Transfer count-1 disks from source to spare.
- Append one source-to-target move for the largest disk.
- Transfer count-1 disks from spare to target.
- Return the accumulated move list.
Visualization
move n-1: A -> B using C
move largest: A -> C
move n-1: B -> C using APython implementation
def hanoi(n):
moves = []
def transfer(count, source, target, spare):
if count == 0:
return
transfer(count - 1, source, spare, target)
moves.append((source, target))
transfer(count - 1, spare, target, source)
transfer(n, 'A', 'C', 'B')
return moves
Worked execution
hanoi(3)
L2 hanoi: moves = []
n=3; moves=[]
L4 transfer: if count == 0:
count=3; source='A'; target='C'; spare='B'; moves=[]
L4 transfer: if count == 0:
count=2; source='A'; target='B'; spare='C'; moves=[]
L4 transfer: if count == 0:
count=1; source='A'; target='C'; spare='B'; moves=[]
L4 transfer: if count == 0:
count=0; source='A'; target='B'; spare='C'; moves=[]
L7 transfer: moves.append((source, target))
count=1; source='A'; target='C'; spare='B'; moves=[('A', 'C')]
L4 transfer: if count == 0:
count=0; source='B'; target='C'; spare='A'; moves=[('A', 'C')]
L6 transfer: transfer(count - 1, source, spare, target)
count=2; source='A'; target='B'; spare='C'; moves=[('A', 'C')]
... 18 intermediate state changes omitted ...
L4 transfer: if count == 0:
count=0; source='B'; target='C'; spare='A'; moves=[('A', 'C'), ('A', 'B'), ('C', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('A', 'C')]
L8 transfer: transfer(count - 1, spare, target, source)
count=2; source='B'; target='C'; spare='A'; moves=[('A', 'C'), ('A', 'B'), ('C', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('A', 'C')]
L8 transfer: transfer(count - 1, spare, target, source)
count=3; source='A'; target='C'; spare='B'; moves=[('A', 'C'), ('A', 'B'), ('C', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('A', 'C')]
L9 hanoi: transfer(n, 'A', 'C', 'B')
n=3; moves=[('A', 'C'), ('A', 'B'), ('C', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('A', 'C')]Result: [('A', 'C'), ('A', 'B'), ('C', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('A', 'C')]. Seven legal moves transfer three disks.
hanoi(1)
L2 hanoi: moves = []
n=1; moves=[]
L4 transfer: if count == 0:
count=1; source='A'; target='C'; spare='B'; moves=[]
L4 transfer: if count == 0:
count=0; source='A'; target='B'; spare='C'; moves=[]
L7 transfer: moves.append((source, target))
count=1; source='A'; target='C'; spare='B'; moves=[('A', 'C')]
L4 transfer: if count == 0:
count=0; source='B'; target='C'; spare='A'; moves=[('A', 'C')]
L9 hanoi: transfer(n, 'A', 'C', 'B')
n=1; moves=[('A', 'C')]Result: [('A', 'C')]. The largest disk is also the only disk.
Why the solution is correct
By induction, the first child legally clears the largest disk, which can then move to the destination. The second child legally places the smaller stack on it. Any solution must clear and restore those smaller disks around the largest move, yielding the same 2*T(n-1)+1 lower bound met by this construction.
Time and space
Exactly 2^n-1 moves: O(2^n) time/output space and O(n) call-stack space. Materializing large n is inherently impractical.
Common mistakes
Peg names change roles between child calls. Forgetting to exchange the spare and target roles produces illegal moves.
Reasoning card
State: transfer(count,source,target,spare) appends moves that relocate exactly that legal stack while preserving disk order.
Invariant: Each call legally transfers its top n-disk stack.
Decision to rehearse: Move n-1 aside, move largest, move n-1 onto it.
Finish: Return the accumulated move list
Transfer exercise
Write a simulator that rejects illegal moves and verifies the final peg contents for every generated small-n sequence.