from collections import deque
class Solution:
def minimumMoves(self, grid):
n = len(grid)
# Each state: (tail_x, tail_y, orientation), 0 = horizontal, 1 = vertical
queue = deque()
visited = set()
# Start: tail at (0,0), horizontal (0), head at (0,1)
queue.append((0, 0, 0, 0)) # x, y, orientation, moves
visited.add((0, 0, 0))
while queue:
x, y, orient, moves = queue.popleft()
# Target: tail at (n-1, n-2), horizontal
if x == n-1 and y == n-2 and orient == 0:
return moves
# Move forward
if orient == 0: # horizontal
# move right
if y+2 < n and grid[x][y+2] == 0 and grid[x][y+1] == 0:
nxt = (x, y+1, 0)
if nxt not in visited:
visited.add(nxt)
queue.append((x, y+1, 0, moves+1))
# move down
if x+1 < n and grid[x+1][y] == 0 and grid[x+1][y+1] == 0:
nxt = (x+1, y, 0)
if nxt not in visited:
visited.add(nxt)
queue.append((x+1, y, 0, moves+1))
# rotate clockwise to vertical
nxt_rot = (x, y, 1)
if nxt_rot not in visited:
visited.add(nxt_rot)
queue.append((x, y, 1, moves+1))
else: # vertical
# move down
if x+2 < n and grid[x+2][y] == 0 and grid[x+1][y] == 0:
nxt = (x+1, y, 1)
if nxt not in visited:
visited.add(nxt)
queue.append((x+1, y, 1, moves+1))
# move right
if y+1 < n and grid[x][y+1] == 0 and grid[x+1][y+1] == 0:
nxt = (x, y+1, 1)
if nxt not in visited:
visited.add(nxt)
queue.append((x, y+1, 1, moves+1))
# rotate counterclockwise to horizontal
nxt_rot = (x, y, 0)
if nxt_rot not in visited:
visited.add(nxt_rot)
queue.append((x, y, 0, moves+1))
return -1
#include <vector>
#include <queue>
#include <tuple>
#include <unordered_set>
using namespace std;
class Solution {
public:
int minimumMoves(vector<vector<int>>& grid) {
int n = grid.size();
queue<tuple<int,int,int,int>> q; // x, y, orientation, moves
set<tuple<int,int,int>> visited;
q.emplace(0, 0, 0, 0);
visited.emplace(0, 0, 0);
while (!q.empty()) {
auto [x, y, orient, moves] = q.front(); q.pop();
if (x == n-1 && y == n-2 && orient == 0) return moves;
if (orient == 0) { // horizontal
// move right
if (y+2 < n && grid[x][y+2] == 0 && grid[x][y+1] == 0) {
auto nxt = make_tuple(x, y+1, 0);
if (!visited.count(nxt)) {
visited.insert(nxt);
q.emplace(x, y+1, 0, moves+1);
}
}
// move down
if (x+1 < n && grid[x+1][y] == 0 && grid[x+1][y+1] == 0) {
auto nxt = make_tuple(x+1, y, 0);
if (!visited.count(nxt)) {
visited.insert(nxt);
q.emplace(x+1, y, 0, moves+1);
}
// rotate clockwise
auto nxt_rot = make_tuple(x, y, 1);
if (!visited.count(nxt_rot)) {
visited.insert(nxt_rot);
q.emplace(x, y, 1, moves+1);
}
}
} else { // vertical
// move down
if (x+2 < n && grid[x+2][y] == 0 && grid[x+1][y] == 0) {
auto nxt = make_tuple(x+1, y, 1);
if (!visited.count(nxt)) {
visited.insert(nxt);
q.emplace(x+1, y, 1, moves+1);
}
}
// move right
if (y+1 < n && grid[x][y+1] == 0 && grid[x+1][y+1] == 0) {
auto nxt = make_tuple(x, y+1, 1);
if (!visited.count(nxt)) {
visited.insert(nxt);
q.emplace(x, y+1, 1, moves+1);
}
// rotate counterclockwise
auto nxt_rot = make_tuple(x, y, 0);
if (!visited.count(nxt_rot)) {
visited.insert(nxt_rot);
q.emplace(x, y, 0, moves+1);
}
}
}
}
return -1;
}
};
import java.util.*;
class Solution {
public int minimumMoves(int[][] grid) {
int n = grid.length;
Queue<int[]> queue = new LinkedList<>();
Set<String> visited = new HashSet<>();
// state: x, y, orientation, moves
queue.offer(new int[]{0, 0, 0, 0});
visited.add("0,0,0");
while (!queue.isEmpty()) {
int[] cur = queue.poll();
int x = cur[0], y = cur[1], orient = cur[2], moves = cur[3];
if (x == n-1 && y == n-2 && orient == 0) return moves;
if (orient == 0) { // horizontal
// move right
if (y+2 < n && grid[x][y+2] == 0 && grid[x][y+1] == 0) {
String nxt = (x)+","+(y+1)+",0";
if (!visited.contains(nxt)) {
visited.add(nxt);
queue.offer(new int[]{x, y+1, 0, moves+1});
}
}
// move down
if (x+1 < n && grid[x+1][y] == 0 && grid[x+1][y+1] == 0) {
String nxt = (x+1)+","+y+",0";
if (!visited.contains(nxt)) {
visited.add(nxt);
queue.offer(new int[]{x+1, y, 0, moves+1});
}
// rotate clockwise
String nxt_rot = x+","+y+",1";
if (!visited.contains(nxt_rot)) {
visited.add(nxt_rot);
queue.offer(new int[]{x, y, 1, moves+1});
}
}
} else { // vertical
// move down
if (x+2 < n && grid[x+2][y] == 0 && grid[x+1][y] == 0) {
String nxt = (x+1)+","+y+",1";
if (!visited.contains(nxt)) {
visited.add(nxt);
queue.offer(new int[]{x+1, y, 1, moves+1});
}
}
// move right
if (y+1 < n && grid[x][y+1] == 0 && grid[x+1][y+1] == 0) {
String nxt = x+","+(y+1)+",1";
if (!visited.contains(nxt)) {
visited.add(nxt);
queue.offer(new int[]{x, y+1, 1, moves+1});
}
// rotate counterclockwise
String nxt_rot = x+","+y+",0";
if (!visited.contains(nxt_rot)) {
visited.add(nxt_rot);
queue.offer(new int[]{x, y, 0, moves+1});
}
}
}
}
return -1;
}
}
/**
* @param {number[][]} grid
* @return {number}
*/
var minimumMoves = function(grid) {
const n = grid.length;
// [x, y, orientation, moves]
let queue = [[0, 0, 0, 0]];
let visited = new Set();
visited.add('0,0,0');
while (queue.length) {
let [x, y, orient, moves] = queue.shift();
if (x === n-1 && y === n-2 && orient === 0) return moves;
if (orient === 0) { // horizontal
// move right
if (y+2 < n && grid[x][y+2] === 0 && grid[x][y+1] === 0) {
let nxt = `${x},${y+1},0`;
if (!visited.has(nxt)) {
visited.add(nxt);
queue.push([x, y+1, 0, moves+1]);
}
}
// move down
if (x+1 < n && grid[x+1][y] === 0 && grid[x+1][y+1] === 0) {
let nxt = `${x+1},${y},0`;
if (!visited.has(nxt)) {
visited.add(nxt);
queue.push([x+1, y, 0, moves+1]);
}
// rotate clockwise
let nxt_rot = `${x},${y},1`;
if (!visited.has(nxt_rot)) {
visited.add(nxt_rot);
queue.push([x, y, 1, moves+1]);
}
}
} else { // vertical
// move down
if (x+2 < n && grid[x+2][y] === 0 && grid[x+1][y] === 0) {
let nxt = `${x+1},${y},1`;
if (!visited.has(nxt)) {
visited.add(nxt);
queue.push([x+1, y, 1, moves+1]);
}
}
// move right
if (y+1 < n && grid[x][y+1] === 0 && grid[x+1][y+1] === 0) {
let nxt = `${x},${y+1},1`;
if (!visited.has(nxt)) {
visited.add(nxt);
queue.push([x, y+1, 1, moves+1]);
}
// rotate counterclockwise
let nxt_rot = `${x},${y},0`;
if (!visited.has(nxt_rot)) {
visited.add(nxt_rot);
queue.push([x, y, 0, moves+1]);
}
}
}
}
return -1;
};
You are given an n x n
binary grid representing a board, where 0
represents an empty cell and 1
represents an obstacle. There is a "snake" of length 2 that starts at the top-left corner of the grid, occupying cells (0,0)
and (0,1)
in a horizontal orientation (tail at (0,0)
, head at (0,1)
). The snake can move by:
The goal is to move the snake to the bottom-right corner in a horizontal orientation, i.e., tail at (n-1, n-2)
and head at (n-1, n-1)
. Return the minimum number of moves required to reach the target position. If it's impossible, return -1
.
Constraints:
When first approaching this problem, you might try to simulate every possible move the snake can make, recursively exploring all paths. However, this brute-force method quickly becomes infeasible due to the exponential number of possible positions and orientations, especially as the grid size increases.
Instead, we notice that the problem is about finding the shortest path from the start to the target, where each state is defined by the snake's tail position and orientation. This is similar to classic shortest path problems in graphs, where each valid snake state is a node, and each legal move is an edge.
Therefore, a more efficient approach is to use Breadth-First Search (BFS), which naturally finds the shortest path in an unweighted graph. By tracking visited states (tail position and orientation), we avoid cycles and redundant work.
Let's break down the algorithm step by step:
x
, y
, orientation
), where (x
, y
) is the tail position and orientation
is 0 for horizontal and 1 for vertical.0, 0
, horizontal) and 0 moves.n-1, n-2
), horizontal), return the number of moves taken to reach it.-1
.This approach is efficient because:
Let's walk through a simple example. Suppose the grid is:
0 0 0 1 1 0 0 0 0
The snake starts at (0,0
, 0,1
), horizontal. The goal is to reach (2,1
, 2,2
), horizontal.
-1
.In this example, it is not possible to reach the target because of the obstacles blocking all valid paths.
In a grid with no obstacles, the snake would proceed right, then down, then possibly rotate to reach the target.
Brute-force approach:
This problem is a classic application of BFS for shortest path finding, but with the twist of modeling a 2-cell snake with orientation. By representing each state as a combination of tail position and orientation, and using BFS with a visited set, we efficiently explore all valid moves without redundancy. The solution is elegant because it transforms a seemingly complex movement puzzle into a well-understood graph traversal problem.