Today, we’re diving into a classic interview problem, Open the Lock. This problem requires us to use Breadth-First Search (BFS) to find the shortest path by simulating a lock mechanism with four wheels. Let’s walk through the problem step-by-step, analyze the solution, and cover key details in the code.
Problem Overview
The problem describes a lock with four rotating wheels, each numbered from 0 to 9. The lock starts at “0000”, and each wheel can be rotated up or down to the next number. For instance, “0” can rotate to “1” or “9”.
We have a list of deadend combinations. If we reach one of these, the lock stops, and we can’t proceed further from that position. Given these constraints, we need to find the minimum number of moves to turn the lock from “0000” to a target combination target
. If it’s impossible, we return -1.
Approach
Since we’re trying to find the shortest path to the target, this problem is a perfect candidate for Breadth-First Search (BFS). BFS explores all paths level by level, ensuring we reach the target in the fewest moves possible.
Step-by-Step Solution Outline:
- Initialization: Store all deadends in a
Set
for quick lookup. - BFS: Begin from “0000” and explore every possible combination by turning each wheel up or down.
- Track Moves: Use a queue to store each combination and the current move count, increasing it by one each level.
Code Implementation with Comments
/**
* @param {string[]} deadends - List of dead-end combinations
* @param {string} target - Target combination
* @return {number} - Minimum moves to reach target from "0000", or -1 if impossible
*/
var openLock = function(deadends, target) {
// Initialize a Set for deadends for quick lookup
const seen = new Set(deadends);
// Handle edge cases: if "0000" or target is in deadends, we can't proceed
if (seen.has(target) || seen.has('0000')) return -1;
// If the start is already the target, no moves are needed
if (target === '0000') return 0;
// Initialize BFS queue and moves counter
const queue = ["0000"];
seen.add("0000"); // Mark "0000" as visited
let steps = 0;
// Main BFS loop
while (queue.length > 0) {
// Number of nodes in the current level
let size = queue.length;
// Process each node in the current level
while (size > 0) {
const current = queue.shift(); // Remove the first element in queue
// If we've reached the target, return the steps taken
if (current === target) return steps;
// Generate all possible next moves and add to queue
const comb = generateComb(current);
for (const next of comb) {
if (!seen.has(next)) { // Only add unvisited nodes
queue.push(next); // Add new combination to queue
seen.add(next); // Mark new combination as visited
}
}
size--; // Decrement level size
}
steps++; // Move to the next level (increment moves)
}
// If BFS completes without finding target, return -1
return -1;
};
/**
* generateComb - Generates all possible next moves for a given lock state
* @param {string} input - Current lock state
* @return {string[]} - Returns all possible next moves
*/
var generateComb = function(input) {
const combinations = [];
// Loop through each of the four wheels
for (let i = 0; i < 4; i++) {
// Get the current digit of the wheel
const digit = parseInt(input[i]);
// Roll the digit up by one (wrap around from 9 to 0)
const up = input.slice(0, i) + ((digit + 1) % 10) + input.slice(i + 1);
combinations.push(up); // Add the new combination to the result array
// Roll the digit down by one (wrap around from 0 to 9)
const down = input.slice(0, i) + ((digit - 1 + 10) % 10) + input.slice(i + 1);
combinations.push(down); // Add the new combination to the result array
}
return combinations;
};
Detailed Code Explanation
- Main Function
openLock
:- The
seen
set stores all visited states to prevent revisiting deadends and previous combinations. - We initialize a
queue
to store each combination and mark “0000” as visited to prevent reprocessing. - The BFS loop processes nodes level by level, allowing us to find the shortest path to
target
.
- The
- Combination Generator
generateComb
:- For each digit in the 4-digit lock, we generate two new states: one by rolling the digit up and one by rolling it down.
- Each new combination is added to the result array, which is then returned to the BFS loop for further processing.
- Core Logic:
- BFS systematically expands from the start state, checking all possible next moves.
- The algorithm uses
steps
to count levels, ensuring that when we reachtarget
, it’s in the minimum moves. - If BFS finishes without finding
target
, we return -1, as it’s impossible to reach.
Example Walkthrough
Consider the case where deadends = ["0201","0101","0102","1212","2002"]
and target = "0202"
:
- Initial State: queue =
["0000"]
, steps =0
- First Level (step = 1): From “0000”, we expand to “1000”, “9000”, “0100”, “0900”… adding all valid moves to the queue.
- Subsequent Levels: We keep expanding outward, avoiding deadends and marking visited states.
- Final Steps: Eventually, we reach “0202” in the minimum moves and return the step count.
Conclusion
This problem is an excellent example of BFS’s utility in finding shortest paths. Key takeaways include:
- Using BFS: For shortest path problems where each move has equal weight, BFS provides an efficient approach.
- Tracking Visited States: By storing visited states, we avoid deadends and reduce redundant processing.
- Generating Moves: By iterating through each digit and generating moves, we simulate all possible paths from a given state.
By practicing with problems like this, you’ll improve your understanding of BFS and how to apply it to real-world scenarios!