The Cat and Mouse II problem involves simulating a two-player game on a grid. The grid is represented as a 2D array of characters, where each cell can be:
'#'
: a wall (cannot be passed)'.'
: an empty cell (can be moved into)'C'
: the starting position of the cat'M'
: the starting position of the mouse'F'
: the foodjump
steps in any of the four cardinal directions (up, down, left, right), but cannot move through walls or outside the grid. The mouse has mouseJump
steps per turn, and the cat has catJump
steps per turn.
At first glance, this problem looks like a classic two-player game with perfect information, much like chess or tic-tac-toe, but on a grid. The main challenge is simulating all possible moves for both players, considering their jump limits and the obstacles (walls).
A brute-force approach would try every possible sequence of moves, but this quickly becomes infeasible due to the exponential number of game states. Instead, we realize this is a minimax problem: the mouse tries to maximize its chances of winning, while the cat tries to minimize them.
We need to model the game as a series of states (positions of mouse and cat, whose turn it is, and turn count), and determine from each state whether the mouse can force a win. To avoid re-computation, we should cache (memoize) the results of previously computed states.
The main conceptual leap is recognizing that this is a game theory problem with recursive state evaluation and memoization.
Let's break down the solution step by step:
Sample Input:
grid = [ "M.C..", "##.##", "..F.." ] catJump = 1 mouseJump = 2Step-by-step:
Brute-Force:
In practice, the constraints on N (grid size) and jump values keep the solution efficient.
The Cat and Mouse II problem is a classic example of a two-player perfect information game that can be solved using recursive minimax with memoization. By modeling each unique state and caching results, we avoid redundant computation and ensure an optimal solution. The key insight is recognizing the structure of the game and leveraging dynamic programming to evaluate game outcomes efficiently. This approach ensures the mouse's victory can be predicted accurately, even with complex movement rules and obstacles.
from functools import lru_cache
class Solution:
def canMouseWin(self, grid, catJump, mouseJump):
ROWS, COLS = len(grid), len(grid[0])
for r in range(ROWS):
for c in range(COLS):
if grid[r][c] == 'M':
mouse_start = (r, c)
if grid[r][c] == 'C':
cat_start = (r, c)
if grid[r][c] == 'F':
food = (r, c)
directions = [(-1,0), (1,0), (0,-1), (0,1)]
MAX_TURNS = 1000
@lru_cache(None)
def dfs(mx, my, cx, cy, turn):
if turn >= MAX_TURNS:
return False
if (mx, my) == (cx, cy):
return False
if (cx, cy) == food:
return False
if (mx, my) == food:
return True
if turn % 2 == 0: # Mouse's turn
for dx, dy in directions + [(0,0)]:
for dist in range(1 if (dx,dy)==(0,0) else mouseJump+1):
nx, ny = mx + dx*dist, my + dy*dist
if 0 <= nx < ROWS and 0 <= ny < COLS and grid[nx][ny] != '#':
if dfs(nx, ny, cx, cy, turn+1):
return True
else:
break
return False
else: # Cat's turn
for dx, dy in directions + [(0,0)]:
for dist in range(1 if (dx,dy)==(0,0) else catJump+1):
nx, ny = cx + dx*dist, cy + dy*dist
if 0 <= nx < ROWS and 0 <= ny < COLS and grid[nx][ny] != '#':
if not dfs(mx, my, nx, ny, turn+1):
return False
else:
break
return True
return dfs(mouse_start[0], mouse_start[1], cat_start[0], cat_start[1], 0)
class Solution {
public:
int ROWS, COLS, MAX_TURNS = 1000;
vector<string> grid;
pair<int,int> food;
int catJump, mouseJump;
int memo[8][8][8][8][2][1050]; // [mx][my][cx][cy][turn%2][turn]
bool dfs(int mx, int my, int cx, int cy, int turn, int isMouseTurn) {
if (turn >= MAX_TURNS)
return false;
if (mx == cx && my == cy)
return false;
if (cx == food.first && cy == food.second)
return false;
if (mx == food.first && my == food.second)
return true;
int &res = memo[mx][my][cx][cy][isMouseTurn][turn];
if (res != -1) return res;
vector<pair<int,int>> dirs = {{-1,0},{1,0},{0,-1},{0,1},{0,0}};
if (isMouseTurn) {
for (auto d : dirs) {
for (int jump = 0; jump <= (d==make_pair(0,0)?0:mouseJump); ++jump) {
int nx = mx + d.first*jump, ny = my + d.second*jump;
if (nx < 0 || nx >= ROWS || ny < 0 || ny >= COLS || grid[nx][ny] == '#') break;
if (dfs(nx, ny, cx, cy, turn+1, 0)) return res = true;
}
}
return res = false;
} else {
for (auto d : dirs) {
for (int jump = 0; jump <= (d==make_pair(0,0)?0:catJump); ++jump) {
int nx = cx + d.first*jump, ny = cy + d.second*jump;
if (nx < 0 || nx >= ROWS || ny < 0 || ny >= COLS || grid[nx][ny] == '#') break;
if (!dfs(mx, my, nx, ny, turn+1, 1)) return res = false;
}
}
return res = true;
}
}
bool canMouseWin(vector<string>& _grid, int _catJump, int _mouseJump) {
grid = _grid;
ROWS = grid.size();
COLS = grid[0].size();
catJump = _catJump;
mouseJump = _mouseJump;
memset(memo, -1, sizeof(memo));
pair<int,int> mouse, cat;
for (int i = 0; i < ROWS; ++i) {
for (int j = 0; j < COLS; ++j) {
if (grid[i][j] == 'M') mouse = {i,j};
if (grid[i][j] == 'C') cat = {i,j};
if (grid[i][j] == 'F') food = {i,j};
}
}
return dfs(mouse.first, mouse.second, cat.first, cat.second, 0, 1);
}
};
class Solution {
final int MAX_TURNS = 1000;
int ROWS, COLS, catJump, mouseJump;
int[][][][][] memo;
int[] food;
String[] grid;
public boolean canMouseWin(String[] grid, int catJump, int mouseJump) {
this.grid = grid;
this.ROWS = grid.length;
this.COLS = grid[0].length();
this.catJump = catJump;
this.mouseJump = mouseJump;
this.memo = new int[ROWS][COLS][ROWS][COLS][MAX_TURNS];
int[] mouse = new int[2], cat = new int[2], food = new int[2];
for (int i = 0; i < ROWS; ++i)
for (int j = 0; j < COLS; ++j) {
if (grid[i].charAt(j) == 'M') {
mouse[0] = i; mouse[1] = j;
}
if (grid[i].charAt(j) == 'C') {
cat[0] = i; cat[1] = j;
}
if (grid[i].charAt(j) == 'F') {
food[0] = i; food[1] = j;
}
}
this.food = food;
return dfs(mouse[0], mouse[1], cat[0], cat[1], 0, true);
}
private boolean dfs(int mx, int my, int cx, int cy, int turn, boolean mouseTurn) {
if (turn >= MAX_TURNS) return false;
if (mx == cx && my == cy) return false;
if (cx == food[0] && cy == food[1]) return false;
if (mx == food[0] && my == food[1]) return true;
int val = memo[mx][my][cx][cy][turn];
if (val != 0) return val == 1;
int[][] dirs = {{-1,0},{1,0},{0,-1},{0,1},{0,0}};
if (mouseTurn) {
for (int[] d : dirs) {
for (int jump = 0; jump <= (d[0]==0 && d[1]==0 ? 0 : mouseJump); ++jump) {
int nx = mx + d[0]*jump, ny = my + d[1]*jump;
if (nx < 0 || nx >= ROWS || ny < 0 || ny >= COLS || grid[nx].charAt(ny) == '#') break;
if (dfs(nx, ny, cx, cy, turn+1, false)) {
memo[mx][my][cx][cy][turn] = 1;
return true;
}
}
}
memo[mx][my][cx][cy][turn] = -1;
return false;
} else {
for (int[] d : dirs) {
for (int jump = 0; jump <= (d[0]==0 && d[1]==0 ? 0 : catJump); ++jump) {
int nx = cx + d[0]*jump, ny = cy + d[1]*jump;
if (nx < 0 || nx >= ROWS || ny < 0 || ny >= COLS || grid[nx].charAt(ny) == '#') break;
if (!dfs(mx, my, nx, ny, turn+1, true)) {
memo[mx][my][cx][cy][turn] = -1;
return false;
}
}
}
memo[mx][my][cx][cy][turn] = 1;
return true;
}
}
}
var canMouseWin = function(grid, catJump, mouseJump) {
const ROWS = grid.length, COLS = grid[0].length;
let mouse, cat, food;
for (let i = 0; i < ROWS; ++i) {
for (let j = 0; j < COLS; ++j) {
if (grid[i][j] === 'M') mouse = [i, j];
if (grid[i][j] === 'C') cat = [i, j];
if (grid[i][j] === 'F') food = [i, j];
}
}
const MAX_TURNS = 1000;
const memo = new Map();
const dirs = [[-1,0],[1,0],[0,-1],[0,1],[0,0]];
function stateKey(mx, my, cx, cy, turn) {
return `${mx},${my},${cx},${cy},${turn}`;
}
function dfs(mx, my, cx, cy, turn) {
if (turn >= MAX_TURNS) return false;
if (mx === cx && my === cy) return false;
if (cx === food[0] && cy === food[1]) return false;
if (mx === food[0] && my === food[1]) return true;
let key = stateKey(mx, my, cx, cy, turn);
if (memo.has(key)) return memo.get(key);
if (turn % 2 === 0) { // Mouse's turn
for (let [dx, dy] of dirs) {
for (let d = 0; d <= ((dx===0 && dy===0) ? 0 : mouseJump); ++d) {
let nx = mx + dx*d, ny = my + dy*d;
if (nx < 0 || nx >= ROWS || ny < 0 || ny >= COLS || grid[nx][ny] === '#') break;
if (dfs(nx, ny, cx, cy, turn+1)) {
memo.set(key, true);
return true;
}
}
}
memo.set(key, false);
return false;
} else { // Cat's turn
for (let [dx, dy] of dirs) {
for (let d = 0; d <= ((dx===0 && dy===0) ? 0 : catJump); ++d) {
let nx = cx + dx*d, ny = cy + dy*d;
if (nx < 0 || nx >= ROWS || ny < 0 || ny >= COLS || grid[nx][ny] === '#') break;
if (!dfs(mx, my, nx, ny, turn+1)) {
memo.set(key, false);
return false;
}
}
}
memo.set(key, true);
return true;
}
}
return dfs(mouse[0], mouse[1], cat[0], cat[1], 0);
};