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;
};