Let's play a game on an undirected graph between two players, Mouse and Cat. The graph is represented such that graph[a]
is a list of all nodes b
that share an edge with node a
.
The Mouse starts at node 1 and goes first. The Cat starts at node 2 and goes second. Node 0 is the Hole.
On each turn, a player must move along an edge from their current location. The Cat cannot move to the Hole (node 0).
The game ends in one of three ways:
Given a graph, and assuming both players play optimally, determine the winner. Return:
Example:
graph = [[2,5],[3],[0,4,5],[1,4,5],[2,3],[0,2,3]]
In this example, the output should be 0 (draw). Can you explain your approach and write code to solve this problem efficiently?
A brute-force solution would involve simulating the game using recursion. The state of the game can be represented by the positions of the mouse and the cat, and whose turn it is. We would explore all possible moves for each player until one of the winning or draw conditions is met.
However, this approach would likely lead to a time limit exceeded error because the game can potentially go on indefinitely if no winning conditions are met, and we don't detect repeated states. This makes the Big O run-time infinite, which is bad.
To avoid recomputing the same game states, we can use dynamic programming with memoization. We maintain a 3D array dp[mouse][cat][turns]
where dp[i][j][k]
stores the result of the game when the mouse is at node i
, the cat is at node j
, and it's turn k
. The value of dp[i][j][k]
can be 1 (mouse wins), 2 (cat wins), or 0 (draw).
We can define a recursive function that uses memoization to explore the game states. The base cases for the recursion are:
2 * n
, where n
is the number of nodes), we declare a draw to avoid infinite recursion.For each game state, we explore the possible moves for the current player and recursively call the function to determine the outcome of the resulting game states. If it's the mouse's turn, we want to find at least one move that leads to a mouse win. If it's the cat's turn, we want to ensure that all possible moves lead to a cat win. If neither of these conditions is met, the game results in a draw.
Edge Cases:
class Solution:
def catMouseGame(self, graph: List[List[int]]) -> int:
n = len(graph)
dp = [[[-1] * (2 * n) for _ in range(n)] for _ in range(n)]
def solve(mouse, cat, turn):
if turn == 2 * n:
return 0
if mouse == 0:
return 1
if mouse == cat:
return 2
if dp[mouse][cat][turn] != -1:
return dp[mouse][cat][turn]
if turn % 2 == 0: # Mouse's turn
result = 2 # Assume cat wins initially
for next_mouse in graph[mouse]:
outcome = solve(next_mouse, cat, turn + 1)
if outcome == 1: # Mouse can win
dp[mouse][cat][turn] = 1
return 1
elif outcome == 0: # Mouse can draw
result = 0
dp[mouse][cat][turn] = result
return result
else: # Cat's turn
result = 1 # Assume mouse wins initially
for next_cat in graph[cat]:
if next_cat != 0:
outcome = solve(mouse, next_cat, turn + 1)
if outcome == 2: # Cat can win
dp[mouse][cat][turn] = 2
return 2
elif outcome == 0: # Cat can draw
result = 0
dp[mouse][cat][turn] = result
return result
return solve(1, 2, 0)
Big O Analysis:
dp
of size N x N x 2N.