Last Updated: March 28, 2026
We are navigating a 4-digit combination lock from "0000" to some target combination. Each move turns exactly one wheel by one position (up or down), and certain combinations are "deadends" that we cannot pass through. We need the shortest sequence of moves, or -1 if it is impossible.
This is really a shortest-path problem in disguise. Think of every possible 4-digit combination as a node in a graph. Two nodes are connected by an edge if they differ by exactly one digit by exactly one position (one wheel turn). The deadends are nodes we must avoid entirely. We need the shortest path from "0000" to the target, which is exactly what BFS does.
The key insight is recognizing the state space. There are only 10^4 = 10,000 possible combinations, and each combination has exactly 8 neighbors (4 wheels, each can go up or down). This is a small, manageable graph, and BFS will find the answer efficiently.
deadends.length <= 500 -- At most 500 blocked states out of 10,000 total. Most of the graph remains navigable.target.length == 4 and each character is a digit -- The state space is fixed at 10^4 = 10,000 states regardless of input size.Whenever you see "minimum number of moves" or "shortest path" in a problem where each move has equal cost, BFS should be your first thought. And that is exactly what this problem is asking.
Picture every possible 4-digit combination ("0000" through "9999") as a node in an enormous graph. Two nodes share an edge if you can get from one to the other with a single wheel turn. From any combination, you can turn any of the 4 wheels up or down by one, giving you exactly 8 neighbors. The deadends are simply nodes we delete from the graph, they cannot be visited.
BFS explores nodes level by level. Level 0 is "0000". Level 1 is all states reachable in one turn. Level 2 is all states reachable in two turns, and so on. The first time BFS reaches the target, we know that is the minimum number of turns. No shorter path exists because BFS explores every possibility of length k before anything of length k+1.
There is one edge case to handle upfront: if "0000" itself is a deadend, we cannot even start, so return -1 immediately.
dead.dead, return -1 immediately.turns = 0.turns. Generate all 8 neighbors (4 wheels x 2 directions). For each neighbor not in dead and not in visited, add it to visited and enqueue it. After processing the entire level, increment turns.Standard BFS explores outward from the source in all directions, even directions that move away from the target. What if we searched from both ends simultaneously, so the two frontiers meet in the middle much sooner?
Standard BFS explores outward from one end, and the number of states at each level grows rapidly. If the shortest path has length d, BFS might explore O(8^d) states before reaching the target (though the 10,000-state cap limits this in practice).
Bidirectional BFS starts from both ends simultaneously. Instead of one frontier growing from "0000" to the target, we maintain two frontiers: one expanding from the source and one from the target. At each step, we expand the smaller frontier. When the two frontiers overlap, we have found the shortest path.
Imagine the shortest path has length 6. Standard BFS explores all states up to distance 6 from the start. Bidirectional BFS only needs to explore states up to distance 3 from each end. Since the number of states grows exponentially with distance (up to 8 neighbors each level), searching to depth 3 from both sides is much cheaper than searching to depth 6 from one side.
Bidirectional BFS is correct because it still explores states level by level from both ends. If the shortest path has length d, then at some level the forward frontier (at distance k from the start) and the backward frontier (at distance d - k from the target) will share a common state. The sum k + (d - k) = d gives us the correct shortest path length.
Expanding the smaller frontier at each step is a heuristic that minimizes the total number of states explored. If one frontier has 10 states and the other has 1,000, expanding the smaller one adds at most 80 new states, while expanding the larger one could add up to 8,000.
dead. If "0000" is in dead, return -1.frontSet = {"0000"} and backSet = {target}. Create a visited set containing both.turns + 1. Otherwise, if valid, add to the next frontier.