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
andv
where an edge[u, v]
exists in the tree - Claiming that
u
is the parent ofv
(meaningu
is closer to the root thanv
)
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 andn-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:
- Traverse the tree to count how many of Bob's guesses are correct when different nodes are chosen as the root
- 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 rootdfs2
: Reuses the count from the parent node and adjusts it based on edge direction changes when moving the root from parent to child
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 nodev
, andu
is one step closer to the root thanv
. - In other words, there must be a directed edge from
u
tov
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 ofj
(edge goes fromi
toj
) - After:
j
is the parent ofi
(edge goes fromj
toi
)
This means:
- If Bob guessed
[i, j]
, this guess was correct before but incorrect after moving the root, so we subtract 1 fromcnt
- If Bob guessed
[j, i]
, this guess was incorrect before but correct after moving the root, so we add 1 tocnt
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
whereg[i]
contains all nodes adjacent to nodei
- 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)
wherei
is the parent ofj
, check if this guess exists ings
- If
(i, j)
is ings
, increment the countercnt
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 childj
:- 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)
- The edge
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 EvaluatorExample 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 takesO(n)
time wheren
is the number of edges - Creating the Counter
gs
from guesses takesO(m)
time wherem
is the number of guesses dfs1
traverses the entire tree once, visiting each node exactly once and checking each edge, takingO(n)
timedfs2
also traverses the entire tree once, visiting each node exactly once. For each edge traversed, it performs constant time operations (updatingcnt
by checking presence ings
), takingO(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, usingO(n)
space - Counter
gs
stores all guesses as key-value pairs, usingO(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
) useO(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
Which data structure is used to implement priority queue?
Recommended Readings
Everything About Trees A tree is a type of graph data structure composed of nodes and edges Its main properties are It is acyclic doesn't contain any cycles There exists a path from the root to any node Has N 1 edges where N is the number of nodes in the tree and
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Want a Structured Path to Master System Design Too? Donât Miss This!