1728. Cat and Mouse II
Problem
A game is played by a cat and a mouse named Cat and Mouse.
The environment is represented by a grid of size rows x cols, where each element is a wall, floor, player (Cat, Mouse), or food.
- Players are represented by the characters
'C'(Cat),'M'(Mouse). - Floors are represented by the character
'.'and can be walked on. - Walls are represented by the character
'#'and cannot be walked on. - Food is represented by the character
'F'and can be walked on. - There is only one of each character
'C','M', and'F'ingrid.
Mouse and Cat play according to the following rules:
- Mouse moves first, then they take turns to move.
- During each turn, Cat and Mouse can jump in one of the four directions (left, right, up, down). They cannot jump over the wall nor outside of the
grid. catJump, mouseJumpare the maximum lengths Cat and Mouse can jump at a time, respectively. Cat and Mouse can jump less than the maximum length.- Staying in the same position is allowed.
- Mouse can jump over Cat.
The game can end in 4 ways:
- If Cat occupies the same position as Mouse, Cat wins.
- If Cat reaches the food first, Cat wins.
- If Mouse reaches the food first, Mouse wins.
- If Mouse cannot get to the food within 1000 turns, Cat wins.
Given a rows x cols matrix grid and two integers catJump and mouseJump, return true if Mouse can win the game if both Cat and Mouse play optimally, otherwise return false.
https://leetcode.cn/problems/cat-and-mouse-ii/
Example 1:

Input:
grid = ["####F","#C...","M...."], catJump = 1, mouseJump = 2
Output:true
Explanation: Cat cannot catch Mouse on its turn nor can it get the food before Mouse.
Example 2:

Input:
grid = ["M.C...F"], catJump = 1, mouseJump = 4
Output:true
Example 3:
Input:
grid = ["M.C...F"], catJump = 1, mouseJump = 3
Output:false
Constraints:
rows == grid.lengthcols = grid[i].length1 <= rows, cols <= 8grid[i][j]consist only of characters'C','M','F','.', and'#'.- There is only one of each character
'C','M', and'F'ingrid. 1 <= catJump, mouseJump <= 8
Test Cases
class Solution:
def canMouseWin(self, grid: List[str], catJump: int, mouseJump: int) -> bool:import pytest
from solution import Solution
@pytest.mark.parametrize('grid, catJump, mouseJump, expected', [
(["####F","#C...","M...."], 1, 2, True),
(["M.C...F"], 1, 4, True),
(["M.C...F"], 1, 3, False),
(["..#C","...#","..M.","#F..","...."], 2, 1, True),
])
@pytest.mark.parametrize('sol', [Solution()])
def test_solution(sol, grid, catJump, mouseJump, expected):
assert sol.canMouseWin(grid, catJump, mouseJump) == expected
Thoughts
913. Cat and Mouse 的进阶版,除了地图和输赢的规则更复杂,还增加了老鼠的步数限制。
整体处理方案完全一致,即先找出所有确定输赢的状态,然后从每个状态倒推其上游状态,直到待处理的状态队列清空。最终看初始状态是否得到了确定的结果。
直接套用 913. Cat and Mouse 的代码,根据本题的具体规则做微调即可。
同样用三元组表示游戏的任何一个状态:state = (mouse, cat, moving) 分别是老鼠所在位置、猫所在位置、当前该谁移动。本题中的位置也是二元组如 (r, c) 表示玩家所在的行和列。
设食物所在的位置为 f0,那么对于任意(不是墙或食物的)位置 (r, c),(f0, (r, c), CAT) 表示老鼠吃到食物即老鼠赢,((r, c), f0, MOUSE) 表示猫吃到食物即猫赢,((r, c), (r, c), *) 表示猫抓到老鼠即猫赢。
设初始时老鼠和猫的位置分别为 m0 和 c0,那么初始状态为 initial = (m0, c0, MOUSE)。
prev_states 方法用来枚举指定状态的所有上游状态,为了减少计算量,本题没再用 generator,而是直接返回上游状态数组并加了缓存避免重复计算。move_cnt 方法返回指定状态的所有下游状态的总数。注意本题允许玩家停在原地不动。
另外题目条件中限制如果老鼠在 1000 步内吃不到食物就算猫赢,不过按照给定的棋盘规模,这种情况很难发生,测试用例中也没有,因此并未实现。如果需要实现,可以在记录每个状态的(最终)输赢结果的同时,记录该状态到实际结果状态的最小步数。
Code
from collections import deque
from functools import cache
Player = int
Position = tuple[int, int]
State = tuple[Position, Position, Player] # (mouse position, cat position, current moving player)
class Solution:
def canMouseWin(self, grid: list[str], catJump: int, mouseJump: int) -> bool:
_, MOUSE, CAT = range(3)
dirs = [(-1, 0), (1, 0), (0, -1), (0, 1)]
m = len(grid)
n = len(grid[0])
winners: dict[State, Player] = {} # state -> winner
queue: list[State] = deque()
# Find the initial state.
m0 = c0 = f0 = (0, 0)
for r, row in enumerate(grid):
for c, cell in enumerate(row):
if cell == 'M':
m0 = (r, c)
elif cell == 'C':
c0 = (r, c)
elif cell == 'F':
f0 = (r, c)
@cache
def prev_states(mouse: Position, cat: Position, moving: Player) -> list[State]:
states = []
if moving == MOUSE:
states.append((mouse, cat, CAT))
for dr, dc in dirs:
r, c = cat
for _ in range(catJump):
r -= dr
c -= dc
if r < 0 or r >= m or c < 0 or c >= n or grid[r][c] == '#':
break
states.append((mouse, (r, c), CAT))
else:
states.append((mouse, cat, MOUSE))
for dr, dc in dirs:
r, c = mouse
for _ in range(mouseJump):
r -= dr
c -= dc
if r < 0 or r >= m or c < 0 or c >= n or grid[r][c] == '#':
break
states.append(((r, c), cat, MOUSE))
return states
def move_cnt(mouse: Position, cat: Position, moving: Player) -> int:
cnt = 1 # 1 for staying at the same position
if moving == MOUSE:
for dr, dc in dirs:
r, c = mouse
for _ in range(mouseJump):
r += dr
c += dc
if r < 0 or r >= m or c < 0 or c >= n or grid[r][c] == '#':
break
cnt += 1
else:
for dr, dc in dirs:
r, c = cat
for _ in range(catJump):
r += dr
c += dc
if r < 0 or r >= m or c < 0 or c >= n or grid[r][c] == '#':
break
cnt += 1
return cnt
# Known winning states.
for r in range(m):
for c in range(n):
if grid[r][c] == '#':
continue
state = (f0, (r, c), CAT)
winners[state] = MOUSE
queue.append(state)
state = ((r, c), f0, MOUSE)
winners[state] = CAT
queue.append(state)
for moving in (MOUSE, CAT):
state = ((r, c), (r, c), moving)
winners[state] = CAT
queue.append(state)
initial = (m0, c0, MOUSE)
doubt_moves: dict[State, int] = {} # state -> number of remaining undetermined moves
while queue:
state = queue.popleft()
winner = winners[state]
if winner == state[2]:
for p_state in prev_states(*state):
if p_state in winners: continue
if p_state not in doubt_moves: doubt_moves[p_state] = move_cnt(*p_state)
doubt_moves[p_state] -= 1
if doubt_moves[p_state] == 0:
if p_state == initial: return winner == MOUSE
winners[p_state] = winner
queue.append(p_state)
else:
for p_state in prev_states(*state):
if p_state in winners: continue
if p_state == initial: return winner == MOUSE
winners[p_state] = winner
queue.append(p_state)
return False # DRAW
评论需要 JavaScript。