Skip to content

1210. Minimum Moves to Reach Target with Rotations

Description

In an n*n grid, there is a snake that spans 2 cells and starts moving from the top left corner at (0, 0) and (0, 1). The grid has empty cells represented by zeros and blocked cells represented by ones. The snake wants to reach the lower right corner at (n-1, n-2) and (n-1, n-1).

In one move the snake can:

  • Move one cell to the right if there are no blocked cells there. This move keeps the horizontal/vertical position of the snake as it is.
  • Move down one cell if there are no blocked cells there. This move keeps the horizontal/vertical position of the snake as it is.
  • Rotate clockwise if it's in a horizontal position and the two cells under it are both empty. In that case the snake moves from (r, c) and (r, c+1) to (r, c) and (r+1, c).
  • Rotate counterclockwise if it's in a vertical position and the two cells to its right are both empty. In that case the snake moves from (r, c) and (r+1, c) to (r, c) and (r, c+1).

Return the minimum number of moves to reach the target.

If there is no way to reach the target, return -1.

 

Example 1:

Input: grid = [[0,0,0,0,0,1],
               [1,1,0,0,1,0],
               [0,0,0,0,1,1],
               [0,0,1,0,1,0],
               [0,1,1,0,0,0],
               [0,1,1,0,0,0]]
Output: 11
Explanation:
One possible solution is [right, right, rotate clockwise, right, down, down, down, down, rotate counterclockwise, right, down].

Example 2:

Input: grid = [[0,0,1,1,1,1],
               [0,0,0,0,1,1],
               [1,1,0,0,0,1],
               [1,1,1,0,0,1],
               [1,1,1,0,0,1],
               [1,1,1,0,0,0]]
Output: 9

 

Constraints:

  • 2 <= n <= 100
  • 0 <= grid[i][j] <= 1
  • It is guaranteed that the snake starts at empty cells.

 

Solutions

Solution: Breadth-First Search

  • Time complexity: O(n2)
  • Space complexity: O(n2)

 

JavaScript

js
/**
 * @param {number[][]} grid
 * @return {number}
 */
const minimumMoves = function (grid) {
  const n = grid.length;
  const visited = new Set(['true,0,1']);
  let queue = [{ isHorizontal: true, snake: [0, 1] }];
  let result = 0;

  const isReach = (head, isHorizontal) => {
    return isHorizontal && head[0] === n - 1 && head[1] === n - 1;
  };

  const moveRight = (head, tail, isHorizontal) => {
    const nextHeadRow = head[0];
    const nextHeadCol = head[1] + 1;
    const headCell = grid[nextHeadRow][nextHeadCol];

    if (headCell !== 0) return null;
    if (isHorizontal) return [nextHeadRow, nextHeadCol];
    const nextTailRow = tail[0];
    const nextTailCol = tail[1] + 1;
    const tailCell = grid[nextTailRow][nextTailCol];

    return tailCell === 0 ? [nextHeadRow, nextHeadCol] : null;
  };

  const moveDown = (head, tail, isHorizontal) => {
    const nextHeadRow = head[0] + 1;
    const nextHeadCol = head[1];
    const headCell = grid[nextHeadRow]?.[nextHeadCol];

    if (headCell !== 0) return null;
    if (!isHorizontal) return [nextHeadRow, nextHeadCol];
    const nextTailRow = tail[0] + 1;
    const nextTailCol = tail[1];
    const tailCell = grid[nextTailRow]?.[nextTailCol];

    return tailCell === 0 ? [nextHeadRow, nextHeadCol] : null;
  };

  const moveRotate = (head, isHorizontal) => {
    let [nextHeadRow, nextHeadCol] = head;

    if (isHorizontal) {
      const throughCell = grid[nextHeadRow + 1]?.[nextHeadCol];

      if (throughCell !== 0) return null;
      nextHeadRow += 1;
      nextHeadCol -= 1;
    } else {
      const throughCell = grid[nextHeadRow][nextHeadCol + 1];

      if (throughCell !== 0) return null;
      nextHeadRow -= 1;
      nextHeadCol += 1;
    }
    const headCell = grid[nextHeadRow]?.[nextHeadCol];

    return headCell === 0 ? [nextHeadRow, nextHeadCol] : null;
  };

  const isCacheVisited = (head, isHorizontal) => {
    const key = `${isHorizontal},${head}`;

    if (visited.has(key)) return true;
    visited.add(key);
    return false;
  };

  while (queue.length) {
    const nextQueue = [];

    result += 1;

    for (const {
      isHorizontal,
      snake: [row, col],
    } of queue) {
      const head = [row, col];
      const tail = isHorizontal ? [row, col - 1] : [row - 1, col];
      const rightSnake = moveRight(head, tail, isHorizontal);
      const downSnake = moveDown(head, tail, isHorizontal);
      const rotateSnake = moveRotate(head, isHorizontal);

      if (rightSnake && !isCacheVisited(rightSnake, isHorizontal)) {
        if (isReach(rightSnake, isHorizontal)) return result;
        nextQueue.push({ isHorizontal, snake: rightSnake });
      }
      if (downSnake && !isCacheVisited(downSnake, isHorizontal)) {
        if (isReach(downSnake, isHorizontal)) return result;
        nextQueue.push({ isHorizontal, snake: downSnake });
      }
      if (rotateSnake && !isCacheVisited(rotateSnake, !isHorizontal)) {
        if (isReach(rotateSnake, !isHorizontal)) return result;
        nextQueue.push({ isHorizontal: !isHorizontal, snake: rotateSnake });
      }
    }
    queue = nextQueue;
  }
  return -1;
};

Released under the MIT license