Quantcast
Channel: BFS shortest path for Google Foobar "Prepare the Bunnies' Escape" - Code Review Stack Exchange
Viewing all articles
Browse latest Browse all 3

Answer by Anirudh Goel for BFS shortest path for Google Foobar "Prepare the Bunnies' Escape"

$
0
0

The approach followed by the question poster and also in the other answers use BFS in a loop to find the answer. If there're a lot of walls that cannot be passed, the time complexity can get quite high. To be precise, the time complexity would be \$O(n * m * j)\$ where \$n\$ and \$m\$ are the number of rows and columns respectively and \$j\$ is the number of walls.

The solution that I propose solves the problem in single BFS loop, so it is much faster than other suggested solutions.

Logic:

The question can be solved using BFS with the following modifications:

  1. Each path that'd otherwise have all 0s in a standard BFS algo can have a single 1.

  2. In the BFS queue, each node, in addition to keeping a track of the x and y coordinates, will also store the number of steps (path length) taken to reach there and if the path for that node has already traversed over a 1 or not. So, each node in the queue will have the format - [[row, col], num_steps, one_traversed].

    So while it may look like we need to store the whole paths in the BFS traversal, what we actually need to store is if a 1 has been traversed in the path or not. So, every node will store that info for itself.

  3. We will maintain a visited grid that will keep a track of all nodes that have been visited (to avoid loops) but here's the tricky part - instead of having only 1s and 0s to represent visited or not, this grid will have 4 possible values:

    • -1 is the initial value
    • 0 means visited by a path having NO 1s (all 0s)
    • 1 means visited by a path having a 1
    • 2 means visited by both types of paths

    Reason:

    While in usual BFS questions, we avoid visiting a node after it has been visited once, in this problem every node in the maze can be visited twice before being marked for not visiting again. This is because:

    • If a node is being visited for the first time by a path consisting of only 0s, it should not be visited again by any another path consisting of only 0s as that other path would either be of the same length (in which case it doesn't matter) or longer (in which case we anyway wouldn't want to consider it as that path is reaching a node which has already been traversed by a shorter path consisting of all 0s).

      Same logic goes for a node that has been visited by a path which had traversed a 1 and is now again being visited by a path having a 1 (we will reject the later path as it is either same or longer than the first path).

    • However if a node that was earlier visited by a path having a 1 is now being visited by a path having only 0s, we want to consider this later path too. Why? Because it is possible that the earlier path which reached this node after traversing a 1 may not be able to reach the destination as it may reach a point where it needs to traverse an additional 1 for reaching the destination but as it has already traversed a 1, it cannot proceed further. So, this new path which may be longer than the earlier one, but hasn't traversed a 1 yet, may be able to traverse the additional 1.

      Example

      Example Image

      In this example while the red path reaches the node [2, 0] first, it is not the correct path as it gets blocked at [3, 0]. Therefore, we have to also consider the green path to pass through [2, 0] even if it is longer than the red one.

And finally, the base case is to stop when you reach the bottom right node in the maze. (The question states that there will always be a solution, so no check for the no solution case.)

Code:

grid = [[0, 0, 0, 0],[1, 1, 1, 0],[0, 0, 0, 0],[1, 1, 1, 1],[0, 1, 1, 1],[0, 0, 0, 0]]  # using the name grid instead of mazenum_rows = len(grid)num_cols = len(grid[0])def is_valid(r, c):    return True if (0 <= r < num_rows and 0 <= c < num_cols) else Falsedef get_neighbours(r, c):    up = [r - 1, c]    down = [r + 1, c]    left = [r, c - 1]    right = [r, c + 1]    neighbours = [down, right, up, left]    valid_neighbour = list()    for neighbour in neighbours:        if is_valid(*neighbour):            valid_neighbour.append(neighbour)    return valid_neighbour# queue format is [[row, col], num_steps, one_traversed]queue = [[[0, 0], 1, 0]]cols = list()visited = list()# visited matrix is used to keep track of visited nodes:# -1 is default# 0 means visited by a path having no 1s# 1 means visited by a path having a 1# 2 means visited by both paths - having 1 and 0sfor j in range(num_rows):    visited.append([-1] * num_cols)visited[0][0] = 0# BFSwhile queue:    current_node = queue.pop(0)    r, c, num_steps, one_traversed = current_node[0][0], current_node[0][        1], current_node[1], current_node[2]    # Base Case    if r == num_rows - 1 and c == num_cols - 1:        print(num_steps)    neighbours = get_neighbours(r, c)    for neighbour in neighbours:        if visited[neighbour[0]][neighbour[1]] in [0, 1]:            # the current node was previously visited with opposite one_traversed value, so consider it            if visited[neighbour[0]][neighbour[1]] != one_traversed:                one_traversed_now = 1 if grid[neighbour[0]][neighbour[1]] == 1 else one_traversed                visited[neighbour[0]][neighbour[1]] = 2                queue.append([[neighbour[0], neighbour[1]], num_steps + 1, one_traversed_now])        elif visited[neighbour[0]][neighbour[1]] == -1:            if grid[neighbour[0]][neighbour[1]] == 1 and one_traversed == 1:                continue            one_traversed_now = 1 if grid[neighbour[0]][neighbour[1]] == 1 else one_traversed            visited[neighbour[0]][neighbour[1]] = one_traversed_now            queue.append([[neighbour[0], neighbour[1]], num_steps + 1, one_traversed_now])

Example test cases:

enter image description here


Viewing all articles
Browse latest Browse all 3

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>