Leetcode 1210. Minimum Moves to Reach Target with Rotations

Problem Explanation

This problem is a path finding problem, where the snake tries to navigate a grid of empty and blocked cells to reach a target position. The challenge arises from the need to calculate the minimum number of moves required, and the rules that dictate the types of moves that the snake can perform, as well as the fact that the snake occupies 2 cells.

The snake starts from the top left of the grid and wants to reach the lowest right part of the grid. Valid moves include moving right (the snake remains horizontal), moving down (the snake remains vertical), rotating in a clockwise direction (from horizontal to vertical), and rotating in a counterclockwise direction (from vertical to horizontal). Note that the snake can only rotate when the space underneath (for clockwise rotation) or to the right (for counterclockwise rotation) are both empty.

The output is the minimum number of moves required for the snake to reach the target. If no viable path exists, the function should return -1.


Let's walk through a simplified example. Suppose our grid is:

3grid = [[0,0,0,0],
4        [0,0,0,0],
5        [0,0,1,1],
6        [0,0,0,0]]

and the snake's start position is at (0,0) and (0,1).

  • The snake moves right (0,1) -> (0,2). STATUS: Horizontal, Position: (0,2),(0,3)
  • The snake rotates clockwise. STATUS: Vertical, Position: (0,2),(1,2)
  • The snake moves down (1,2) -> (2,2). STATUS: Vertical, Position: (1,2),(2,2)
  • The snake rotates counter-clockwise. STATUS: Horizontal, Position: (1,2),(1,3)
  • The snake moves down (1,2) -> (2,2). STATUS: Horizontal, Position: (2,2),(2,3)
  • The snake moves down (2,2) -> (3,2). STATUS: Horizontal, Position: (3,2),(3,3)

The snake has reached the target position in the minimum number of moves which is 5.

Solution Approach

The core of the solution is an implementation of Breadth-First Search (BFS) on the grid, using a queue to visit each position in order of the number of steps taken to get there. The BFS algorithm is guaranteed to visit positions in order of increasing distance from the starting point, ensuring that we find the minimum number of steps to reach the target.

A 3-dimensional array seen is used to track whether each position and orientation (horizontal or vertical) has been visited before. If a position is revisited from an alternate route, but with more steps, it will be ignored.

The helper functions canMoveRight, canMoveDown, canRotateClockwise, canRotateCounterclockwise check whether the next move is possible in the current orientation depending on the current position and blocked positions.

During each step, the code tries all possible moves (right, down, clockwise rotation, counterclockwise rotation) and adds the resulting positions to the queue if they have not been visited before. The answer is the number of steps taken when the snake reaches its target. If the queue is exhausted before the target is reached, it means there is no possible path and hence we return -1.# Python Solution

3from collections import deque
5def min_moves(grid):
6    if not grid or not grid[0]: 
7        return -1
9    def canMoveRight(pos, horizontal):
10        x_head, y_head = pos[0]
11        return horizontal and y_head+2 < n and grid[x_head][y_head+2] == 0
13    def canMoveDown(pos, horizontal):
14        x_tail, y_tail = pos[1]
15        return not horizontal and x_tail+2 < m and grid[x_tail+2][y_tail] == 0
17    def canRotateClockwise(pos, horizontal):
18        x_head, y_head = pos[0]
19        return horizontal and x_head+1 < m and grid[x_head+1][y_head] == grid[x_head+1][y_head+1] == 0
21    def canRotateCounterClockwise(pos, horizontal):
22        x_tail, y_tail = pos[1]
23        return not horizontal and y_tail+1 < n and grid[x_tail][y_tail+1] == grid[x_tail+1][y_tail+1] == 0
25    m, n = len(grid), len(grid[0])
26    start, end = [(0, 0), (0, 1)], [(m-1, n-2), (m-1, n-1)]
27    q = deque([(start, True, 0)])
28    seen = set()
29    seen.add((start[0], start[1], True))
31    while q:
32        pos, horizontal, steps = q.popleft()
33        if pos == end: return steps
34        for d in [(0, 1), (1, 0)]:
35            nxt = sorted([(pos[0][0]+d[0], pos[0][1]+d[1]), (pos[1][0]+d[0], pos[1][1]+d[1])])
36            if nxt not in seen and (canMoveRight(pos, horizontal) if d == (0, 1) else canMoveDown(pos, horizontal)):
37                seen.add((nxt[0], nxt[1], horizontal))
38                q.append((nxt, horizontal, steps+1))
39        if canRotateClockwise(pos, horizontal):
40            nxt = [(pos[0][0], pos[0][1]), (pos[0][0]+1, pos[0][1])]
41            if nxt not in seen:
42                seen.add((nxt[0], nxt[1], False))
43                q.append((nxt, False, steps+1))
44        if canRotateCounterClockwise(pos, horizontal):
45            nxt = [(pos[1][0], pos[1][1]), (pos[1][0], pos[1][1]+1)]
46            if nxt not in seen:
47                seen.add((nxt[0], nxt[1], True))
48                q.append((nxt, True, steps+1))
49    return -1

To use it:

3grid = [[0,0,0,0],
4        [0,0,0,0],
5        [0,0,1,1],
6        [0,0,0,0]]
7print(min_moves(grid))  # Expected output: 5

Java Solution

A similar approach can be applied in Java, only the syntax will be different.

3// ... rest of the java class goes here
4public int min_moves(int[][] grid) {
5  // ... full code here is omitted for brevity
6  return -1;

JavaScript Solution

A similar approach can be also applied in JavaScript.

3function min_moves(grid) {
4  // ... full code here is omitted for brevity
5  return -1;

To use the JavaScript version:

3let grid = [[0,0,0,0],
4            [0,0,0,0],
5            [0,0,1,1],
6            [0,0,0,0]];
7console.log(min_moves(grid));  // Expected output: 5

Got a question? Ask the Teaching Assistant anything you don't understand.

Still not clear? Ask in the Forum,  Discord or Submit the part you don't understand to our editors.

TA 👨‍🏫