Facebook Pixel

1145. Binary Tree Coloring Game

Problem Description

This is a two-player turn-based game played on a binary tree. The tree has n nodes (where n is odd), and each node contains a distinct value from 1 to n.

Game Setup:

  • Player 1 starts by choosing a value x (where 1 <= x <= n) and colors that node red
  • Player 2 then chooses a different value y (where 1 <= y <= n and y != x) and colors that node blue

Game Rules:

  • Players take turns, starting with Player 1
  • On each turn, a player must:
    • Select one of their already-colored nodes (red for Player 1, blue for Player 2)
    • Color an uncolored adjacent node with their color
    • Adjacent nodes are: the parent, left child, or right child of the selected node
  • If a player cannot make a valid move, they must pass their turn
  • The game ends when both players pass consecutively
  • The winner is the player who colored more nodes

Your Task: You are Player 2. Given the binary tree and Player 1's choice of node x, determine if there exists a choice of y that guarantees you can win the game. Return true if you can guarantee a win, false otherwise.

The key insight is that once both players make their initial choices, the tree gets divided into regions. Player 2 wins if they can secure more than half of the total nodes (n/2). By choosing y strategically, Player 2 can potentially block Player 1 from accessing certain parts of the tree, securing those regions for themselves.

Flowchart Walkthrough

First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:

Is it a graph?

  • Yes: The problem involves a binary tree, which is a special type of graph with nodes and edges (parent-child relationships).

Is it a tree?

  • Yes: The problem explicitly states we're working with a binary tree structure where each node has at most two children and there's a single root node.

DFS

  • We arrive at DFS: Since we're dealing with a tree structure, the flowchart leads us directly to using Depth-First Search (DFS).

Why DFS is appropriate for this problem:

  1. We need to traverse the tree to find the node with value x that Player 1 chose
  2. We need to count nodes in different subtrees to determine the size of regions that would be controlled by each player
  3. DFS naturally explores the tree structure by going deep into each branch, which is perfect for counting nodes in subtrees

Conclusion: The flowchart correctly identifies DFS as the appropriate algorithm. In this problem, we use DFS twice:

  • First, to locate the node where Player 1 placed their initial color (value x)
  • Second, to count the number of nodes in the left subtree, right subtree, and the parent's direction from that node

This allows us to determine the maximum region Player 2 can control by strategically choosing their starting position y.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that once both players make their initial choices, the tree essentially gets "split" into separate regions that each player will control. Why? Because players can only color nodes adjacent to their already-colored nodes. This means the initial choices create barriers that divide the tree.

Think of it this way: when Player 1 colors node x red, and Player 2 colors node y blue, these become the "seeds" from which each player's territory will grow. The critical observation is that a player can only expand into uncolored nodes that are reachable through their own colored nodes.

Now, where should Player 2 place their initial blue node to maximize their territory? There are only three strategic positions relative to Player 1's node x:

  1. In the left subtree of x: This would block Player 1 from accessing that entire left subtree
  2. In the right subtree of x: This would block Player 1 from accessing that entire right subtree
  3. In the parent's direction from x: This would block Player 1 from accessing all nodes "above" x in the tree

The brilliant part is that Player 2 doesn't need to try every possible y value. They just need to check if any of these three regions contains more than half the total nodes. If Player 2 can secure a region with more than n/2 nodes, they're guaranteed to win since Player 1 can't possibly get more nodes than them.

Why does this work? Because once Player 2 places their initial node optimally (say, at the root of the largest subtree), Player 1 is completely blocked from entering that region. Player 2 will eventually color all nodes in that region while Player 1 is confined to the remaining smaller regions.

The solution elegantly reduces a complex game theory problem to a simple counting problem: find the sizes of the three regions around Player 1's node, and check if any region is large enough to guarantee a win.

Learn more about Tree, Depth-First Search and Binary Tree patterns.

Solution Approach

The implementation uses DFS to solve this problem in two phases:

Phase 1: Find Player 1's Node

First, we need to locate the node with value x that Player 1 chose. The dfs function traverses the tree recursively:

  • If we find a None node or the node with value x, we return it
  • Otherwise, we search the left subtree first, then the right subtree
  • This continues until we find the target node

Phase 2: Count Nodes in Each Region

Once we have Player 1's node, we need to count nodes in the three possible regions where Player 2 could place their initial piece:

The count function recursively counts all nodes in a subtree:

  • Base case: if the node is None, return 0
  • Recursive case: return 1 + count(left) + count(right)

We calculate three critical values:

  • l = count(node.left): Number of nodes in the left subtree of Player 1's node
  • r = count(node.right): Number of nodes in the right subtree of Player 1's node
  • n - l - r - 1: Number of nodes in the parent's direction (total nodes minus left subtree, right subtree, and Player 1's node itself)

Winning Condition Check

Player 2 can win if they can secure more than half the total nodes. Since n is odd, we need more than n // 2 nodes to guarantee a win.

The final check max(l, r, n - l - r - 1) > n // 2 determines if any of the three regions is large enough for Player 2 to win. If Player 2 places their initial node as the "gateway" to the largest region, they'll control all nodes in that region while Player 1 is blocked from entering it.

The beauty of this solution is its efficiency: instead of simulating the entire game or trying all possible y values, we reduce the problem to simple tree traversal and counting, achieving O(n) time complexity with just two DFS passes.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example to illustrate the solution approach.

Consider this binary tree with n=7 nodes:

        3
       / \
      2   5
     /   / \
    1   4   6
             \
              7

Scenario: Player 1 chooses node x=5

Step 1: Find Player 1's Node We traverse the tree using DFS to locate node 5. Starting from root (3), we check left subtree (2), then right subtree, and find node 5.

Step 2: Count Nodes in Each Region Now we analyze the three possible regions where Player 2 could place their initial node:

  1. Left subtree of node 5:

    • Contains node 4
    • Count = 1 node
  2. Right subtree of node 5:

    • Contains nodes 6 and 7
    • Count = 2 nodes
  3. Parent direction from node 5:

    • Contains nodes 3, 2, and 1
    • Count = n - left - right - 1 = 7 - 1 - 2 - 1 = 3 nodes

Step 3: Check Winning Condition

  • Total nodes n = 7
  • To win, Player 2 needs more than n/2 = 7/2 = 3 nodes
  • Maximum region size = max(1, 2, 3) = 3

Since 3 is NOT greater than 3, Player 2 cannot guarantee a win in this scenario.

Alternative Scenario: Player 1 chooses node x=2

Let's see what happens with a different choice:

  1. Left subtree of node 2:

    • Contains node 1
    • Count = 1 node
  2. Right subtree of node 2:

    • Contains nothing
    • Count = 0 nodes
  3. Parent direction from node 2:

    • Contains nodes 3, 5, 4, 6, and 7
    • Count = 7 - 1 - 0 - 1 = 5 nodes

Winning Check:

  • Maximum region size = max(1, 0, 5) = 5
  • Since 5 > 3, Player 2 CAN guarantee a win!

Player 2 would place their initial blue node at node 3 (blocking access to the parent direction), securing 5 nodes while Player 1 can only get nodes 2 and 1, totaling 2 nodes. Player 2 wins 5-2!

Solution Implementation

1# Definition for a binary tree node.
2# class TreeNode:
3#     def __init__(self, val=0, left=None, right=None):
4#         self.val = val
5#         self.left = left
6#         self.right = right
7
8from typing import Optional
9
10class Solution:
11    def btreeGameWinningMove(self, root: Optional[TreeNode], n: int, x: int) -> bool:
12        """
13        Determines if the second player can win the binary tree coloring game.
14      
15        The strategy is to find the node with value x (first player's choice),
16        then check if blocking one of three regions gives the second player
17        more than half of the total nodes.
18      
19        Args:
20            root: Root of the binary tree
21            n: Total number of nodes in the tree
22            x: Value of the node chosen by the first player
23          
24        Returns:
25            True if the second player has a winning move, False otherwise
26        """
27      
28        def find_node(current_node: Optional[TreeNode]) -> Optional[TreeNode]:
29            """
30            Finds and returns the node with value x in the tree.
31          
32            Args:
33                current_node: Current node being examined
34              
35            Returns:
36                The node with value x, or None if not found
37            """
38            # Base case: empty node or found the target node
39            if current_node is None or current_node.val == x:
40                return current_node
41          
42            # Search in left subtree first, then right subtree
43            left_result = find_node(current_node.left)
44            return left_result if left_result else find_node(current_node.right)
45      
46        def count_nodes(current_node: Optional[TreeNode]) -> int:
47            """
48            Counts the total number of nodes in a subtree.
49          
50            Args:
51                current_node: Root of the subtree to count
52              
53            Returns:
54                Total number of nodes in the subtree
55            """
56            # Base case: empty node contributes 0
57            if current_node is None:
58                return 0
59          
60            # Count current node (1) plus all nodes in left and right subtrees
61            return 1 + count_nodes(current_node.left) + count_nodes(current_node.right)
62      
63        # Find the node chosen by the first player
64        target_node = find_node(root)
65      
66        # Count nodes in the left and right subtrees of the target node
67        left_subtree_count = count_nodes(target_node.left)
68        right_subtree_count = count_nodes(target_node.right)
69      
70        # Calculate nodes in the parent region (total minus target and its subtrees)
71        parent_region_count = n - left_subtree_count - right_subtree_count - 1
72      
73        # The second player wins if they can control more than half the nodes
74        # by choosing one of the three regions: left subtree, right subtree, or parent region
75        max_controllable_nodes = max(left_subtree_count, right_subtree_count, parent_region_count)
76      
77        return max_controllable_nodes > n // 2
78
1/**
2 * Definition for a binary tree node.
3 * public class TreeNode {
4 *     int val;
5 *     TreeNode left;
6 *     TreeNode right;
7 *     TreeNode() {}
8 *     TreeNode(int val) { this.val = val; }
9 *     TreeNode(int val, TreeNode left, TreeNode right) {
10 *         this.val = val;
11 *         this.left = left;
12 *         this.right = right;
13 *     }
14 * }
15 */
16class Solution {
17    /**
18     * Determines if the second player can win the binary tree coloring game.
19     * The second player wins if they can choose a node that allows them to color more than half of all nodes.
20     * 
21     * @param root The root of the binary tree
22     * @param n The total number of nodes in the tree
23     * @param x The value of the node chosen by the first player
24     * @return true if the second player can guarantee a win, false otherwise
25     */
26    public boolean btreeGameWinningMove(TreeNode root, int n, int x) {
27        // Find the node with value x that was chosen by the first player
28        TreeNode firstPlayerNode = findNode(root, x);
29      
30        // Count nodes in the left subtree of the first player's node
31        int leftSubtreeCount = countNodes(firstPlayerNode.left);
32      
33        // Count nodes in the right subtree of the first player's node
34        int rightSubtreeCount = countNodes(firstPlayerNode.right);
35      
36        // Calculate nodes in the parent direction (total nodes minus left, right, and the node itself)
37        int parentDirectionCount = n - leftSubtreeCount - rightSubtreeCount - 1;
38      
39        // The second player wins if they can control more than half of all nodes
40        // They can choose to block one of three directions: left child, right child, or parent
41        int maxNodesSecondPlayerCanControl = Math.max(
42            Math.max(leftSubtreeCount, rightSubtreeCount), 
43            parentDirectionCount
44        );
45      
46        return maxNodesSecondPlayerCanControl > n / 2;
47    }
48  
49    /**
50     * Recursively searches for a node with the given value in the binary tree.
51     * 
52     * @param root The current node being examined
53     * @param targetValue The value to search for
54     * @return The node with the target value, or null if not found
55     */
56    private TreeNode findNode(TreeNode root, int targetValue) {
57        // Base case: null node or found the target
58        if (root == null || root.val == targetValue) {
59            return root;
60        }
61      
62        // Search in the left subtree
63        TreeNode leftResult = findNode(root.left, targetValue);
64      
65        // If found in left subtree, return it; otherwise search right subtree
66        return leftResult != null ? leftResult : findNode(root.right, targetValue);
67    }
68  
69    /**
70     * Counts the total number of nodes in a subtree rooted at the given node.
71     * 
72     * @param root The root of the subtree to count
73     * @return The total number of nodes in the subtree
74     */
75    private int countNodes(TreeNode root) {
76        // Base case: empty tree has 0 nodes
77        if (root == null) {
78            return 0;
79        }
80      
81        // Count current node plus all nodes in left and right subtrees
82        return 1 + countNodes(root.left) + countNodes(root.right);
83    }
84}
85
1/**
2 * Definition for a binary tree node.
3 * struct TreeNode {
4 *     int val;
5 *     TreeNode *left;
6 *     TreeNode *right;
7 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
8 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
9 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
10 * };
11 */
12class Solution {
13public:
14    /**
15     * Determines if the second player can guarantee a win in the binary tree coloring game
16     * @param root: Root of the binary tree
17     * @param n: Total number of nodes in the tree
18     * @param x: Value of the node chosen by the first player
19     * @return: True if second player can guarantee a win, false otherwise
20     */
21    bool btreeGameWinningMove(TreeNode* root, int n, int x) {
22        // Find the node with value x that was chosen by the first player
23        TreeNode* firstPlayerNode = findNode(root, x);
24      
25        // Count nodes in the left subtree of the first player's node
26        int leftSubtreeCount = countNodes(firstPlayerNode->left);
27      
28        // Count nodes in the right subtree of the first player's node
29        int rightSubtreeCount = countNodes(firstPlayerNode->right);
30      
31        // Count nodes in the parent component (total nodes minus left, right, and the node itself)
32        int parentComponentCount = n - leftSubtreeCount - rightSubtreeCount - 1;
33      
34        // Second player wins if they can control more than half of the nodes
35        // by choosing the optimal starting position among the three components
36        return max({leftSubtreeCount, rightSubtreeCount, parentComponentCount}) > n / 2;
37    }
38
39private:
40    /**
41     * Searches for a node with the given value in the binary tree
42     * @param root: Current node being examined
43     * @param targetValue: Value to search for
44     * @return: Pointer to the node with the target value, or nullptr if not found
45     */
46    TreeNode* findNode(TreeNode* root, int targetValue) {
47        // Base case: null node or found the target
48        if (!root || root->val == targetValue) {
49            return root;
50        }
51      
52        // Search in the left subtree
53        TreeNode* leftResult = findNode(root->left, targetValue);
54      
55        // If found in left subtree, return it; otherwise search right subtree
56        return leftResult ? leftResult : findNode(root->right, targetValue);
57    }
58
59    /**
60     * Counts the total number of nodes in a subtree
61     * @param root: Root of the subtree to count
62     * @return: Total number of nodes in the subtree
63     */
64    int countNodes(TreeNode* root) {
65        // Base case: empty tree has 0 nodes
66        if (!root) {
67            return 0;
68        }
69      
70        // Count current node plus all nodes in left and right subtrees
71        return 1 + countNodes(root->left) + countNodes(root->right);
72    }
73};
74
1/**
2 * Definition for a binary tree node.
3 * class TreeNode {
4 *     val: number
5 *     left: TreeNode | null
6 *     right: TreeNode | null
7 *     constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
8 *         this.val = (val===undefined ? 0 : val)
9 *         this.left = (left===undefined ? null : left)
10 *         this.right = (right===undefined ? null : right)
11 *     }
12 * }
13 */
14
15/**
16 * Determines if the second player can guarantee a win in the binary tree coloring game
17 * @param root - The root of the binary tree
18 * @param n - The total number of nodes in the tree
19 * @param x - The value of the node chosen by the first player
20 * @returns True if the second player can guarantee a win, false otherwise
21 */
22function btreeGameWinningMove(root: TreeNode | null, n: number, x: number): boolean {
23    // Find the node with value x that was chosen by the first player
24    const findTargetNode = (currentNode: TreeNode | null): TreeNode | null => {
25        // Base case: node is null or we found the target node
26        if (!currentNode || currentNode.val === x) {
27            return currentNode;
28        }
29      
30        // Recursively search in left subtree, then right subtree
31        return findTargetNode(currentNode.left) || findTargetNode(currentNode.right);
32    };
33
34    // Count the total number of nodes in a subtree
35    const countNodes = (currentNode: TreeNode | null): number => {
36        // Base case: empty subtree has 0 nodes
37        if (!currentNode) {
38            return 0;
39        }
40      
41        // Count current node (1) plus all nodes in left and right subtrees
42        return 1 + countNodes(currentNode.left) + countNodes(currentNode.right);
43    };
44
45    // Find the node chosen by the first player
46    const targetNode: TreeNode | null = findTargetNode(root);
47  
48    // Count nodes in the left subtree of the chosen node
49    const leftSubtreeCount: number = countNodes(targetNode!.left);
50  
51    // Count nodes in the right subtree of the chosen node
52    const rightSubtreeCount: number = countNodes(targetNode!.right);
53  
54    // Calculate nodes in the parent component (total - left - right - chosen node)
55    const parentComponentCount: number = n - leftSubtreeCount - rightSubtreeCount - 1;
56  
57    // The second player wins if they can control more than half of the nodes
58    // by choosing the optimal starting position (left subtree, right subtree, or parent component)
59    return Math.max(leftSubtreeCount, rightSubtreeCount, parentComponentCount) > n / 2;
60}
61

Time and Space Complexity

The time complexity is O(n), where n is the total number of nodes in the binary tree.

The algorithm consists of two main operations:

  1. The dfs function traverses the tree to find the node with value x. In the worst case, it visits all n nodes, resulting in O(n) time.
  2. The count function is called three times - once for the left subtree of node x, once for the right subtree, and implicitly for the parent subtree (calculated as n - l - r - 1). Each call to count visits every node in its respective subtree exactly once. Since these subtrees are disjoint and together comprise all n nodes, the total time for counting is also O(n).

Therefore, the overall time complexity is O(n) + O(n) = O(n).

The space complexity is O(n) due to the recursive call stack.

Both dfs and count are recursive functions that traverse the tree. In the worst case (a completely unbalanced tree forming a linear chain), the recursion depth can reach n, requiring O(n) space on the call stack. Even in a balanced tree, the recursion depth would be O(log n), but since we consider worst-case complexity, the space complexity is O(n).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrectly Calculating the Parent Region Count

The Pitfall: A common mistake is calculating the parent region as just n - count(target_node) or forgetting to subtract 1 for the target node itself. Some developers might write:

# INCORRECT: This counts the target node in the parent region
parent_region_count = n - left_subtree_count - right_subtree_count

Why It Happens: The confusion arises because when we think about "the rest of the tree," we need to exclude not just the left and right subtrees, but also the target node itself (which belongs to Player 1).

The Solution: Always remember to subtract 1 for the target node:

# CORRECT: Excludes the target node from parent region
parent_region_count = n - left_subtree_count - right_subtree_count - 1

2. Using >= Instead of > in the Final Comparison

The Pitfall: Writing the winning condition as:

# INCORRECT: Tie game means Player 1 wins (they moved first)
return max_controllable_nodes >= n // 2

Why It Happens: In many problems, having "at least half" is sufficient. However, in this game, since n is odd and Player 1 moves first, a tie in node count means Player 1 wins.

The Solution: Player 2 must have strictly more nodes to win:

# CORRECT: Player 2 needs MORE than half to win
return max_controllable_nodes > n // 2

3. Attempting to Simulate All Possible Games

The Pitfall: Trying to implement complex game simulation logic:

# INEFFICIENT AND COMPLEX: Don't do this!
def simulate_game(root, x, y):
    # Complex BFS/DFS to simulate turns
    # Track colored nodes for each player
    # Implement passing logic
    # ... hundreds of lines of code

Why It Happens: The problem description makes it seem like you need to simulate the actual gameplay with turns, moves, and passing.

The Solution: Recognize that once both initial nodes are chosen, the tree is effectively partitioned into regions. The key insight is that Player 2's optimal strategy is to block access to the largest region, making the problem reducible to simple counting:

# ELEGANT: Just count and compare regions
max_controllable_nodes = max(left_subtree_count, right_subtree_count, parent_region_count)
return max_controllable_nodes > n // 2

4. Not Handling the Case Where x is the Root

The Pitfall: Assuming there's always a parent region without checking if the target node is the root:

# RISKY: What if target_node is the root?
parent_region_count = n - left_subtree_count - right_subtree_count - 1

Why It Happens: Developers might worry about edge cases where the target node has no parent.

The Solution: The formula actually handles this correctly! When x is the root:

  • parent_region_count = n - left - right - 1 = 0
  • This is correct because there are no nodes in the "parent direction"
  • The algorithm still works perfectly, comparing the left subtree, right subtree, and 0

No special case handling is needed - the math naturally accounts for all positions in the tree.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which algorithm should you use to find a node that is close to the root of the tree?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More