Facebook Pixel

2581. Count Number of Possible Root Nodes

Problem Description

Alice has an undirected tree with n nodes labeled from 0 to n - 1. The tree is given as a 2D array edges where edges[i] = [ai, bi] indicates an edge between nodes ai and bi.

Alice wants Bob to find which node is the root of her tree. Bob makes several guesses about the parent-child relationships in the tree. Each guess consists of:

  • Choosing two distinct integers u and v where an edge [u, v] exists in the tree
  • Claiming that u is the parent of v (meaning u is closer to the root than v)

Bob's guesses are given as a 2D array guesses where guesses[j] = [uj, vj] means Bob guessed that uj is the parent of vj.

Alice tells Bob that at least k of his guesses are correct.

The task is to find how many different nodes could possibly be the root of Alice's tree, given that at least k of Bob's guesses must be true. Return 0 if no such tree exists.

For example, if Bob guesses that node 1 is the parent of node 2, this guess would be correct if:

  • The root is node 1 (or any node where 1 is closer to the root than 2)
  • The path from the root to node 2 passes through node 1

The key insight is that changing which node is the root changes which parent-child relationships are true in the tree. A guess [u, v] is correct for a particular root if, when traversing from that root, we reach u before v along the path to v.

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 explicitly involves an undirected tree with n nodes and edges connecting them. A tree is a special type of graph (connected and acyclic).

Is it a tree?

  • Yes: The problem states we have an undirected tree with n nodes and n-1 edges, which is the definition of a tree structure.

DFS

  • Yes: Since we're dealing with a tree structure, DFS (Depth-First Search) is the appropriate algorithm.

Conclusion: The flowchart leads us directly to using DFS for this tree problem.

Why DFS is the Right Choice

The problem requires us to:

  1. Traverse the tree to count how many of Bob's guesses are correct when different nodes are chosen as the root
  2. Efficiently re-root the tree and recalculate the count of correct guesses

DFS is ideal here because:

  • We need to traverse the entire tree structure from different potential root nodes
  • We need to maintain parent-child relationships as we traverse
  • The re-rooting technique (also known as "rerooting DP" or "tree DP with root change") naturally uses DFS to:
    • First DFS: Calculate the initial count of correct guesses with node 0 as root
    • Second DFS: Dynamically adjust the count as we conceptually move the root to each node

The solution uses two DFS traversals:

  • dfs1: Counts correct guesses when node 0 is the root
  • dfs2: Reuses the count from the parent node and adjusts it based on edge direction changes when moving the root from parent to child
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that when we change which node is the root of a tree, the parent-child relationships change throughout the tree. However, we don't need to rebuild the entire tree for each potential root - we can be clever about it.

Let's think about what happens when Bob makes a guess [u, v] (meaning u is the parent of v):

  • This guess is correct only if, in the rooted tree, node u is actually on the path from the root to node v, and u is one step closer to the root than v.
  • In other words, there must be a directed edge from u to v when we orient the tree edges away from the root.

Now, here's the clever part: instead of checking all n nodes as potential roots independently (which would be inefficient), we can use a technique called "rerooting" or "tree DP with root change."

Start by picking any node as the initial root (say node 0). Count how many of Bob's guesses are correct with this root. Let's call this count cnt.

When we move the root from a node i to its adjacent node j, only the edge between i and j changes direction:

  • Before: i is the parent of j (edge goes from i to j)
  • After: j is the parent of i (edge goes from j to i)

This means:

  • If Bob guessed [i, j], this guess was correct before but incorrect after moving the root, so we subtract 1 from cnt
  • If Bob guessed [j, i], this guess was incorrect before but correct after moving the root, so we add 1 to cnt

By doing a DFS traversal and updating cnt as we "move" the root through the tree, we can efficiently calculate the number of correct guesses for each possible root. We count how many nodes give us at least k correct guesses.

The formula for updating the count when moving root from i to j is: new_cnt = old_cnt - (guess[i,j] exists ? 1 : 0) + (guess[j,i] exists ? 1 : 0)

This approach runs in O(n) time since we visit each node once in each DFS, making it much more efficient than the naive O(n²) approach of rebuilding the tree for each potential root.

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

Solution Approach

The solution implements the tree DP with root change technique using two DFS traversals:

Step 1: Build the Graph and Process Guesses

  • Convert the edge list to an adjacency list g where g[i] contains all nodes adjacent to node i
  • Store all guesses in a hash map gs as (parent, child) pairs for O(1) lookup
g = defaultdict(list)
for a, b in edges:
    g[a].append(b)
    g[b].append(a)
gs = Counter((u, v) for u, v in guesses)

Step 2: First DFS - Count Correct Guesses with Node 0 as Root The dfs1 function traverses the tree with node 0 as the root and counts how many guesses are correct:

  • For each edge (i, j) where i is the parent of j, check if this guess exists in gs
  • If (i, j) is in gs, increment the counter cnt
def dfs1(i, fa):
    nonlocal cnt
    for j in g[i]:
        if j != fa:  # Avoid going back to parent
            cnt += gs[(i, j)]  # Check if this parent-child guess exists
            dfs1(j, i)  # Recursively process child with i as parent

Step 3: Second DFS - Reroot and Count Valid Roots The dfs2 function performs the rerooting technique:

  • Start with the count from node 0 as root
  • For each node, check if cnt >= k to determine if it's a valid root
  • When moving the root from node i to its child j:
    • The edge (i, j) reverses to (j, i)
    • Subtract 1 if guess (i, j) exists (was correct, now incorrect)
    • Add 1 if guess (j, i) exists (was incorrect, now correct)
def dfs2(i, fa):
    nonlocal ans, cnt
    ans += cnt >= k  # Count this node if it has enough correct guesses
    for j in g[i]:
        if j != fa:
            # Adjust count for moving root from i to j
            cnt -= gs[(i, j)]  # Remove the i->j guess
            cnt += gs[(j, i)]  # Add the j->i guess
          
            dfs2(j, i)  # Process with j as potential root
          
            # Restore count when backtracking
            cnt -= gs[(j, i)]
            cnt += gs[(i, j)]

The restoration step is crucial - after exploring the subtree rooted at j, we need to restore cnt to its original value before processing the next child of i.

Time Complexity: O(n + m) where n is the number of nodes and m is the number of guesses Space Complexity: O(n + m) for storing the graph and guess hash map

The beauty of this approach is that we only need to track the changes in count as we conceptually move the root, rather than recalculating everything from scratch for each potential root.

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 small example to illustrate the solution approach.

Example Input:

  • Tree edges: [[0,1], [1,2], [1,3]] (forms a tree with node 1 connected to 0, 2, and 3)
  • Bob's guesses: [[1,2], [0,1], [3,1]] (Bob guesses 1→2, 0→1, and 3→1 are parent-child relationships)
  • k = 2 (at least 2 guesses must be correct)

Tree Structure:

    0
    |
    1
   / \
  2   3

Step 1: Build Graph and Process Guesses

  • Graph adjacency list: {0: [1], 1: [0,2,3], 2: [1], 3: [1]}
  • Guess map: {(1,2): 1, (0,1): 1, (3,1): 1}

Step 2: First DFS with Root = 0 Starting from node 0, we traverse and count correct guesses:

  • Visit 0 → 1: Check if (0,1) is a guess? Yes! cnt = 1
  • From 1, visit 1 → 2: Check if (1,2) is a guess? Yes! cnt = 2
  • From 1, visit 1 → 3: Check if (1,3) is a guess? No. cnt = 2

With node 0 as root, 2 guesses are correct: (0,1) and (1,2).

Step 3: Second DFS - Reroot and Check Each Node

Starting at node 0 with cnt = 2:

  • Node 0: cnt = 2 >= 2 ✓ Valid root! ans = 1

Move root from 0 to 1:

  • Edge (0,1) reverses to (1,0)
  • Remove guess (0,1): cnt = 2 - 1 = 1
  • Add guess (1,0): Not in guesses, so cnt = 1 + 0 = 1
  • Node 1: cnt = 1 < 2 ✗ Not valid

Move root from 1 to 2:

  • Edge (1,2) reverses to (2,1)
  • Remove guess (1,2): cnt = 1 - 1 = 0
  • Add guess (2,1): Not in guesses, so cnt = 0 + 0 = 0
  • Node 2: cnt = 0 < 2 ✗ Not valid
  • Backtrack: Restore cnt = 1

Move root from 1 to 3:

  • Edge (1,3) reverses to (3,1)
  • Remove guess (1,3): Not in guesses, so cnt = 1 - 0 = 1
  • Add guess (3,1): Yes, it's a guess! cnt = 1 + 1 = 2
  • Node 3: cnt = 2 >= 2 ✓ Valid root! ans = 2

Result: 2 nodes (0 and 3) can be valid roots.

Verification:

  • With root = 0: Correct guesses are (0,1) and (1,2) = 2 correct ✓
  • With root = 3: Correct guesses are (3,1) and (1,2) = 2 correct ✓
  • With root = 1: Only (1,2) is correct = 1 correct ✗
  • With root = 2: Only (0,1) is correct = 1 correct ✗

The rerooting technique efficiently found that nodes 0 and 3 satisfy the requirement of having at least k=2 correct guesses.

Solution Implementation

1from typing import List
2from collections import defaultdict, Counter
3
4
5class Solution:
6    def rootCount(
7        self, edges: List[List[int]], guesses: List[List[int]], k: int
8    ) -> int:
9        # Build adjacency list for the tree
10        graph = defaultdict(list)
11        for node_a, node_b in edges:
12            graph[node_a].append(node_b)
13            graph[node_b].append(node_a)
14      
15        # Convert guesses to a set/counter for O(1) lookup
16        # Each guess is a directed edge (parent, child)
17        guess_set = Counter((parent, child) for parent, child in guesses)
18      
19        # First DFS: Count correct guesses when root is node 0
20        correct_count = 0
21      
22        def count_correct_guesses(node: int, parent: int) -> None:
23            """
24            Count how many guesses are correct when treating node 0 as root.
25            A guess (u, v) is correct if u is parent of v in the rooted tree.
26            """
27            nonlocal correct_count
28          
29            for neighbor in graph[node]:
30                if neighbor != parent:
31                    # Check if edge from node to neighbor matches any guess
32                    if (node, neighbor) in guess_set:
33                        correct_count += guess_set[(node, neighbor)]
34                    # Recursively process the subtree
35                    count_correct_guesses(neighbor, node)
36      
37        # Initial count with node 0 as root
38        count_correct_guesses(0, -1)
39      
40        # Second DFS: Re-root the tree and count valid roots
41        valid_roots = 0
42      
43        def reroot_and_count(node: int, parent: int) -> None:
44            """
45            Use rerooting technique to efficiently count correct guesses
46            for each possible root without recalculating from scratch.
47            """
48            nonlocal valid_roots, correct_count
49          
50            # Check if current node as root has at least k correct guesses
51            if correct_count >= k:
52                valid_roots += 1
53          
54            # Try each child as the new root
55            for neighbor in graph[node]:
56                if neighbor != parent:
57                    # When moving root from node to neighbor:
58                    # - Edge (node, neighbor) changes from parent->child to child->parent
59                    # - Lose correct guess if (node, neighbor) was guessed
60                    # - Gain correct guess if (neighbor, node) was guessed
61                  
62                    # Update count before recursion
63                    correct_count -= guess_set.get((node, neighbor), 0)
64                    correct_count += guess_set.get((neighbor, node), 0)
65                  
66                    # Recursively process with neighbor as new root
67                    reroot_and_count(neighbor, node)
68                  
69                    # Restore count after recursion (backtrack)
70                    correct_count += guess_set.get((node, neighbor), 0)
71                    correct_count -= guess_set.get((neighbor, node), 0)
72      
73        # Start rerooting from node 0
74        reroot_and_count(0, -1)
75      
76        return valid_roots
77
1class Solution {
2    // Graph adjacency list representation
3    private List<Integer>[] graph;
4    // Map to store guesses: key is encoded edge (parent -> child), value is count
5    private Map<Long, Integer> guessMap = new HashMap<>();
6    // Count of valid root nodes
7    private int validRootCount;
8    // Minimum correct guesses threshold
9    private int threshold;
10    // Current count of correct guesses
11    private int correctGuessCount;
12    // Number of nodes in the tree
13    private int nodeCount;
14
15    public int rootCount(int[][] edges, int[][] guesses, int k) {
16        this.threshold = k;
17        this.nodeCount = edges.length + 1;
18      
19        // Initialize adjacency list for undirected graph
20        graph = new List[nodeCount];
21        Arrays.setAll(graph, index -> new ArrayList<>());
22      
23        // Build the undirected graph from edges
24        for (int[] edge : edges) {
25            int nodeA = edge[0];
26            int nodeB = edge[1];
27            graph[nodeA].add(nodeB);
28            graph[nodeB].add(nodeA);
29        }
30      
31        // Store all guesses in a map for O(1) lookup
32        // Each guess represents a directed edge (parent -> child)
33        for (int[] guess : guesses) {
34            int parent = guess[0];
35            int child = guess[1];
36            guessMap.merge(encodeEdge(parent, child), 1, Integer::sum);
37        }
38      
39        // First DFS: Calculate correct guesses when node 0 is root
40        calculateInitialCorrectGuesses(0, -1);
41      
42        // Second DFS: Re-root the tree and count valid roots
43        countValidRoots(0, -1);
44      
45        return validRootCount;
46    }
47
48    /**
49     * First DFS to calculate the number of correct guesses when node 0 is the root
50     * @param currentNode Current node being visited
51     * @param parent Parent of the current node (-1 if root)
52     */
53    private void calculateInitialCorrectGuesses(int currentNode, int parent) {
54        for (int child : graph[currentNode]) {
55            if (child != parent) {
56                // Check if the edge (currentNode -> child) is a correct guess
57                correctGuessCount += guessMap.getOrDefault(encodeEdge(currentNode, child), 0);
58                calculateInitialCorrectGuesses(child, currentNode);
59            }
60        }
61    }
62
63    /**
64     * Second DFS to re-root the tree and count all valid roots
65     * Uses dynamic programming to efficiently update the count when changing root
66     * @param currentNode Current node being considered as root
67     * @param parent Parent of the current node (-1 if root)
68     */
69    private void countValidRoots(int currentNode, int parent) {
70        // Check if current node as root has enough correct guesses
71        if (correctGuessCount >= threshold) {
72            validRootCount++;
73        }
74      
75        // Try each child as the new root
76        for (int child : graph[currentNode]) {
77            if (child != parent) {
78                // When moving root from currentNode to child:
79                // - Edge (currentNode -> child) becomes (child -> currentNode)
80                // - Lose correct guesses for (currentNode -> child)
81                // - Gain correct guesses for (child -> currentNode)
82              
83                int lostGuesses = guessMap.getOrDefault(encodeEdge(currentNode, child), 0);
84                int gainedGuesses = guessMap.getOrDefault(encodeEdge(child, currentNode), 0);
85              
86                // Update the count for the new root
87                correctGuessCount = correctGuessCount - lostGuesses + gainedGuesses;
88              
89                // Recursively process with child as new root
90                countValidRoots(child, currentNode);
91              
92                // Restore the count when backtracking
93                correctGuessCount = correctGuessCount - gainedGuesses + lostGuesses;
94            }
95        }
96    }
97
98    /**
99     * Encodes a directed edge into a unique long value
100     * @param from Source node
101     * @param to Destination node
102     * @return Encoded edge as a long value
103     */
104    private long encodeEdge(int from, int to) {
105        return 1L * from * nodeCount + to;
106    }
107}
108
1class Solution {
2public:
3    int rootCount(vector<vector<int>>& edges, vector<vector<int>>& guesses, int k) {
4        int n = edges.size() + 1;  // Total number of nodes
5      
6        // Build adjacency list for the tree
7        vector<vector<int>> graph(n);
8        for (auto& edge : edges) {
9            int u = edge[0], v = edge[1];
10            graph[u].push_back(v);
11            graph[v].push_back(u);
12        }
13      
14        // Store guesses in a hash map for O(1) lookup
15        // Key: encoded edge (parent * n + child), Value: count
16        unordered_map<long long, int> guessMap;
17        auto encodeEdge = [&](int parent, int child) -> long long {
18            return 1LL * parent * n + child;
19        };
20      
21        for (auto& guess : guesses) {
22            int parent = guess[0], child = guess[1];
23            guessMap[encodeEdge(parent, child)]++;
24        }
25      
26        int result = 0;        // Count of valid roots
27        int correctGuesses = 0; // Current number of correct guesses
28      
29        // First DFS: Calculate correct guesses when node 0 is the root
30        function<void(int, int)> calculateInitialGuesses = [&](int node, int parent) {
31            for (int child : graph[node]) {
32                if (child != parent) {
33                    // Check if this parent-child relationship matches a guess
34                    correctGuesses += guessMap[encodeEdge(node, child)];
35                    calculateInitialGuesses(child, node);
36                }
37            }
38        };
39      
40        // Second DFS: Re-root the tree and count valid roots
41        function<void(int, int)> rerootAndCount = [&](int node, int parent) {
42            // Check if current node as root has at least k correct guesses
43            if (correctGuesses >= k) {
44                result++;
45            }
46          
47            // Try each child as the new root
48            for (int child : graph[node]) {
49                if (child != parent) {
50                    // When moving root from node to child:
51                    // - Edge (node -> child) becomes (child -> node)
52                    int lostGuesses = guessMap[encodeEdge(node, child)];  // Lost when edge flips
53                    int gainedGuesses = guessMap[encodeEdge(child, node)]; // Gained when edge flips
54                  
55                    // Update correct guess count
56                    correctGuesses = correctGuesses - lostGuesses + gainedGuesses;
57                  
58                    // Recursively process with child as new root
59                    rerootAndCount(child, node);
60                  
61                    // Restore correct guess count when backtracking
62                    correctGuesses = correctGuesses - gainedGuesses + lostGuesses;
63                }
64            }
65        };
66      
67        // Start with node 0 as root
68        calculateInitialGuesses(0, -1);
69        rerootAndCount(0, -1);
70      
71        return result;
72    }
73};
74
1/**
2 * Counts the number of valid root nodes based on edges and guesses
3 * @param edges - Array of edge connections in the tree
4 * @param guesses - Array of guessed parent-child relationships
5 * @param k - Minimum number of correct guesses required
6 * @returns Number of valid root nodes
7 */
8function rootCount(edges: number[][], guesses: number[][], k: number): number {
9    const nodeCount = edges.length + 1;
10  
11    // Build adjacency list for the tree
12    const adjacencyList: number[][] = Array.from({ length: nodeCount }, () => []);
13  
14    // Store guesses as a map for O(1) lookup
15    // Key: encoded edge (parent * nodeCount + child), Value: count
16    const guessMap: Map<number, number> = new Map();
17  
18    // Helper function to encode an edge into a single number
19    const encodeEdge = (parent: number, child: number): number => {
20        return parent * nodeCount + child;
21    };
22  
23    // Build the bidirectional graph from edges
24    for (const [nodeA, nodeB] of edges) {
25        adjacencyList[nodeA].push(nodeB);
26        adjacencyList[nodeB].push(nodeA);
27    }
28  
29    // Store all guesses in the map
30    for (const [parent, child] of guesses) {
31        const encodedEdge = encodeEdge(parent, child);
32        guessMap.set(encodedEdge, (guessMap.get(encodedEdge) || 0) + 1);
33    }
34  
35    let validRootCount = 0;
36    let correctGuessCount = 0;
37  
38    /**
39     * First DFS: Calculate correct guesses when node 0 is the root
40     * @param currentNode - Current node being visited
41     * @param parentNode - Parent of the current node (-1 for root)
42     */
43    const calculateInitialGuesses = (currentNode: number, parentNode: number): void => {
44        for (const childNode of adjacencyList[currentNode]) {
45            if (childNode !== parentNode) {
46                // Check if this edge matches any guess
47                correctGuessCount += guessMap.get(encodeEdge(currentNode, childNode)) || 0;
48                calculateInitialGuesses(childNode, currentNode);
49            }
50        }
51    };
52  
53    /**
54     * Second DFS: Re-root the tree and count valid roots
55     * @param currentNode - Current node being considered as root
56     * @param parentNode - Parent of the current node (-1 for initial root)
57     */
58    const countValidRoots = (currentNode: number, parentNode: number): void => {
59        // Check if current node as root has enough correct guesses
60        if (correctGuessCount >= k) {
61            validRootCount++;
62        }
63      
64        for (const childNode of adjacencyList[currentNode]) {
65            if (childNode !== parentNode) {
66                // When moving root from currentNode to childNode:
67                // - Edge (currentNode -> childNode) becomes (childNode -> currentNode)
68                const forwardEdgeCount = guessMap.get(encodeEdge(currentNode, childNode)) || 0;
69                const reverseEdgeCount = guessMap.get(encodeEdge(childNode, currentNode)) || 0;
70              
71                // Update correct guess count for the new root
72                correctGuessCount -= forwardEdgeCount;
73                correctGuessCount += reverseEdgeCount;
74              
75                // Recursively check childNode as root
76                countValidRoots(childNode, currentNode);
77              
78                // Restore correct guess count when backtracking
79                correctGuessCount -= reverseEdgeCount;
80                correctGuessCount += forwardEdgeCount;
81            }
82        }
83    };
84  
85    // Start with node 0 as root to calculate initial correct guesses
86    calculateInitialGuesses(0, -1);
87  
88    // Try each node as root and count valid ones
89    countValidRoots(0, -1);
90  
91    return validRootCount;
92}
93

Time and Space Complexity

Time Complexity: O(n + m)

The algorithm consists of building the graph and two DFS traversals:

  • Building the adjacency list g from edges takes O(n) time where n is the number of edges
  • Creating the Counter gs from guesses takes O(m) time where m is the number of guesses
  • dfs1 traverses the entire tree once, visiting each node exactly once and checking each edge, taking O(n) time
  • dfs2 also traverses the entire tree once, visiting each node exactly once. For each edge traversed, it performs constant time operations (updating cnt by checking presence in gs), taking O(n) time overall

Total time complexity: O(n) + O(m) + O(n) + O(n) = O(n + m)

Space Complexity: O(n + m)

The space usage includes:

  • Adjacency list g stores all edges bidirectionally, using O(n) space
  • Counter gs stores all guesses as key-value pairs, using O(m) space
  • Recursion stack for DFS can go up to the height of the tree, which in worst case (skewed tree) is O(n)
  • Other variables (cnt, ans) use O(1) space

Total space complexity: O(n) + O(m) + O(n) = O(n + m)

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

Common Pitfalls

Pitfall 1: Forgetting to Restore State During Backtracking

The Problem: A critical mistake is forgetting to restore the correct_count after exploring each subtree in the rerooting phase. Without proper restoration, the count becomes corrupted and produces incorrect results for subsequent nodes.

Incorrect Code:

def reroot_and_count(node: int, parent: int) -> None:
    nonlocal valid_roots, correct_count
  
    if correct_count >= k:
        valid_roots += 1
  
    for neighbor in graph[node]:
        if neighbor != parent:
            # Update count before recursion
            correct_count -= guess_set.get((node, neighbor), 0)
            correct_count += guess_set.get((neighbor, node), 0)
          
            reroot_and_count(neighbor, node)
          
            # MISSING: Restoration of correct_count!

Why It Fails: Without restoration, the correct_count accumulates changes incorrectly. When processing the second child of a node, it uses the modified count from the first child rather than the original count for that node.

Solution: Always restore the state after recursion:

# Update count before recursion
correct_count -= guess_set.get((node, neighbor), 0)
correct_count += guess_set.get((neighbor, node), 0)

reroot_and_count(neighbor, node)

# Restore count after recursion
correct_count += guess_set.get((node, neighbor), 0)
correct_count -= guess_set.get((neighbor, node), 0)

Pitfall 2: Using Wrong Data Structure for Guess Lookup

The Problem: Using a list to check if a guess exists leads to O(m) lookup time for each edge, resulting in O(n*m) overall complexity.

Incorrect Code:

# Inefficient: Using list for guesses
guesses_list = guesses  # Direct use of input list

def count_correct_guesses(node: int, parent: int) -> None:
    nonlocal correct_count
    for neighbor in graph[node]:
        if neighbor != parent:
            # O(m) operation for each edge check!
            if [node, neighbor] in guesses_list:
                correct_count += 1

Solution: Use a set or Counter for O(1) lookup:

# Efficient: Convert to set/Counter
guess_set = Counter((parent, child) for parent, child in guesses)

# Now checking is O(1)
if (node, neighbor) in guess_set:
    correct_count += guess_set[(node, neighbor)]

Pitfall 3: Incorrect Edge Direction Handling

The Problem: Confusing which edge direction represents a correct guess when rerooting. Remember that a guess (u, v) means "u is the parent of v".

Incorrect Logic:

# Wrong: Adding when should subtract, or vice versa
correct_count += guess_set.get((node, neighbor), 0)  # Should be subtract!
correct_count -= guess_set.get((neighbor, node), 0)  # Should be add!

Why It's Wrong: When moving root from node to neighbor:

  • The edge (node, neighbor) was a parent-child relationship (node was parent)
  • After rerooting, it becomes child-parent (neighbor is now parent)
  • If (node, neighbor) was a correct guess before, it's now incorrect
  • If (neighbor, node) was an incorrect guess before, it's now correct

Solution: Always remember the transformation rule:

# When moving root from node to neighbor:
correct_count -= guess_set.get((node, neighbor), 0)  # Was correct, now wrong
correct_count += guess_set.get((neighbor, node), 0)  # Was wrong, now correct

Pitfall 4: Not Handling Duplicate Guesses

The Problem: If the same guess appears multiple times in the input, treating it as appearing only once will give wrong counts.

Incorrect Code:

# Using set instead of Counter loses duplicate information
guess_set = set((parent, child) for parent, child in guesses)

# This only adds 1 even if the guess appears multiple times
if (node, neighbor) in guess_set:
    correct_count += 1  # Should add the actual count!

Solution: Use Counter to preserve the count of each guess:

guess_set = Counter((parent, child) for parent, child in guesses)
correct_count += guess_set[(node, neighbor)]  # Adds the actual frequency
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which data structure is used to implement priority queue?


Recommended Readings

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

Load More