Similar Problems

Similar Problems not available

Open The Lock - Leetcode Solution


  • jpmorgan

LeetCode:  Open The Lock Leetcode Solution

Difficulty: Medium

Topics: string hash-table array breadth-first-search  

The "Open the Lock" problem on LeetCode is a problem that requires us to find the minimum number of turns required to open a lock given a start configuration and a target configuration, with some restrictions on the moves that can be made.

Here's a detailed solution to the problem:

First, we need to define the problem more precisely. We are given a lock with four dials, each of which can be set to one of the ten numbers from 0 to 9. We are also given a starting configuration and a target configuration, both represented as strings of four numbers. We can change the lock's configuration by turning any one of the dials one step up or down, wrapping around from 9 to 0 and vice versa. We can make any number of such moves, but we must not turn a dial beyond the extremes of 0 and 9. Our goal is to find the minimum number of moves required to change the starting configuration to the target configuration.

The problem can be solved using a modified form of Breadth-First Search (BFS) or "Bi-Directional BFS". In BFS, we start at the root node and explore all the neighboring nodes at the current depth before moving on to the deeper levels. In Bi-Directional BFS, we start from both the start node and the target node, and keep expanding outwards from both sides simultaneously until we find a common node. The Bi-Directional BFS algorithm is more efficient than regular BFS because it looks for the solution from both directions, which reduces the search space.

Here's the code for the Bi-Directional BFS solution:

from queue import Queue

class Solution:
    def openLock(self, deadends: List[str], target: str) -> int:
        if target == "0000":
            return 0
        dead_set = set(deadends)
        if "0000" in dead_set:
            return -1
        q_start = Queue()
        q_end = Queue()
        depth_start = { "0000": 0 }
        depth_end = { target: 0 }
        while not q_start.empty() and not q_end.empty():
            res = self.bfs(q_start, depth_start, depth_end, dead_set)
            if res != -1:
                return res
            res = self.bfs(q_end, depth_end, depth_start, dead_set)
            if res != -1:
                return res
        return -1
    def bfs(self, q, depth, depth_other, dead_set):
        curr = q.get()
        for i in range(4):
            for move in [-1, 1]:
                next_val = (int(curr[i]) + move) % 10
                next = curr[:i] + str(next_val) + curr[i+1:]
                if next in dead_set or next in depth:
                if next in depth_other:
                    return depth[curr] + 1 + depth_other[next]
                depth[next] = depth[curr] + 1
        return -1

The function openLock takes a list of deadends and the target string as arguments, and returns the minimum number of turns required to reach the target or -1 if it's not possible. We first check if the target is already "0000" and if the starting position is a deadend. If either of these conditions is true, we return the respective result (-1 or 0). We then create two queues, one for the start node and one for the target node, and initialize the depths of the start node and the target node to zero.

We then enter a loop that continues until either of the two queues becomes empty. In each iteration, we call the function bfs with the start queue, start depth, target depth, and the set of deadends. This function expands the nodes from the start queue, adds them to the start depth dictionary, and checks if any of the newly generated nodes are already in the target depth dictionary. If so, it means we've found a common node, and we return the sum of the depths of the two nodes plus 1 (for the current move).

We then repeat the same process for the end queue and the end depth dictionary. Finally, if we haven't found the solution yet, we return -1.

The bfs function takes a queue, a dictionary of depths, a dictionary of depths of the other end, and a set of deadends as arguments. It removes the first item from the queue, generates the new nodes by adding or subtracting one from each digit, generates the next node in a loop. If the node is not in the deadend set or the current depth dictionary, we check if it's already in the depth dictionary of the other end. If so, we return the sum of the current node's depth and the other node's depth plus one. Otherwise, we add the node to the current depth dictionary and the queue.

The time complexity of this algorithm is O(b^(d/2)), where b is the branching factor and d is the maximum depth of the solution. Since the branching factor is 8 in this case (we have eight possible neighbors for a node, four with the dial increased and four with the dial decreased), and the maximum depth is 10^4 (i.e. all 10,000 possible configurations), the time complexity is quite reasonable.

Overall, this solution provides an efficient and elegant way to solve the "Open the Lock" problem on LeetCode, handling all possible scenarios correctly.

Open The Lock Solution Code