Open the Lock
A 4-wheel lock starts at 0000; find the minimum number of turns to reach target, avoiding any state in deadends.
Intuition
The trap that catches people fastest is forgetting to check whether the start 0000 is itself a deadend - if it is, the lock cannot be turned even once, and the answer is -1 before any BFS runs at all, no matter how close 0000 looks to target. Beyond that, this is BFS over an implicit state graph again: each 4-digit state has 8 neighbors, one wheel turned up or down, and the wheels wrap, so turning a wheel down from 0 lands on 9, not -1. Generate the 8 neighbors on the fly, skip deadends and visited states, and BFS layer count is the minimum number of turns.
BFS over lock states, with deadends folded into the visited set at initialisation. That single trick means the expansion loop needs no separate deadend test — an unreachable state and an already-seen state are handled identically.
Approach
Guard the start
If '0000' is itself in deadends, the lock is stuck before a single turn, so return -1 immediately.
Generate 8 neighbors with wrap-around
For each of the 4 wheels, turning up adds 1 mod 10 and turning down subtracts 1 mod 10 (so 0 wraps to 9). A candidate state is valid if it is not a deadend and not already visited.
BFS layer = turn count
Track turns from '0000' (0 turns). The moment target is popped from the queue, its turn count is the minimum, since BFS explores shortest paths first.
Solution & live demo
Common pitfalls
Not wrapping the digits
nd = digit + delta
nd = (digit + delta) % 10
The wheels are circular: 9 turns to 0 and 0 turns to 9. Without the modulo the digit goes out of range and the state string becomes invalid or throws.
Checking deadends separately in the loop
if cand not in visited and cand not in dead:
visited = {'0000'} | deadTwo conditions to keep in sync at every expansion. Seeding the visited set with the deadends makes them unreachable by construction, with one lookup instead of two.
Missing the case where the start is a deadend
q = deque([('0000', 0)])if '0000' in dead:
return -1If the initial state is blocked the lock can never be turned, but the BFS would still expand from it since it was enqueued before any check. The guard has to come before the seed.
Edge cases
Return -1 before any traversal.
Zero turns needed, found immediately.
BFS queue empties without reaching target; return -1.
Turning down from 0 must land on 9, not -1 or an invalid digit.