Taro Logo

Cat and Mouse

Hard
Google logo
Google
0 views
Topics:
GraphsDynamic Programming

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:

  1. If the Cat and Mouse occupy the same node, the Cat wins.
  2. If the Mouse reaches the Hole (node 0), the Mouse wins.
  3. If the same position is repeated, it's a draw.

Given a graph, and assuming both players play optimally, determine the winner. Return:

  • 1 if the Mouse wins.
  • 2 if the Cat wins.
  • 0 if it's a draw.

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?

Solution


Brute Force Solution

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.

Optimal Solution: Dynamic Programming with Memoization

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:

  • If the mouse reaches the hole (node 0), the mouse wins.
  • If the cat catches the mouse (mouse and cat are at the same node), the cat wins.
  • If the number of turns exceeds a certain limit (e.g., 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:

  • If the graph is empty or contains only the hole, the game is not possible, but given the constraints, this should not happen.
  • If the mouse and cat start at the same node, the cat wins immediately.
  • If the mouse is adjacent to the hole at the start, the mouse wins immediately.
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:

  • Time Complexity: O(N^3), where N is the number of nodes in the graph. This is because we have N^2 possible positions for the mouse and cat, and for each position, we explore up to 2N turns.
  • Space Complexity: O(N^3), due to the memoization table dp of size N x N x 2N.