Count Number of Possible Root Nodes
HardPrompt
Alice has an undirected tree with n nodes labeled from 0 to n - 1. The tree is represented as a 2D integer array edges of length n - 1 where edges[i] = [ai, bi] indicates that there is an edge between nodes ai and bi in the tree.
Alice wants Bob to find the root of the tree. She allows Bob to make several guesses about her tree. In one guess, he does the following:
- Chooses two distinct integers
uandvsuch that there exists an edge[u, v]in the tree. - He tells Alice that
uis the parent ofvin the tree.
Bob's guesses are represented by a 2D integer array guesses where guesses[j] = [uj, vj] indicates Bob guessed uj to be the parent of vj.
Alice being lazy, does not reply to each of Bob's guesses, but just says that at least k of his guesses are true.
Given the 2D integer arrays edges, guesses and the integer k, return the number of possible nodes that can be the root of Alice's tree. If there is no such tree, return 0.
Example 1:

Input: edges = [[0,1],[1,2],[1,3],[4,2]], guesses = [[1,3],[0,1],[1,0],[2,4]], k = 3
Output: 3
Explanation:
Root = 0, correct guesses = [1,3], [0,1], [2,4]
Root = 1, correct guesses = [1,3], [1,0], [2,4]
Root = 2, correct guesses = [1,3], [1,0], [2,4]
Root = 3, correct guesses = [1,0], [2,4]
Root = 4, correct guesses = [1,3], [1,0]
Considering 0, 1, or 2 as root node leads to 3 correct guesses.Example 2:

Input: edges = [[0,1],[1,2],[2,3],[3,4]], guesses = [[1,0],[3,4],[2,1],[3,2]], k = 1
Output: 5
Explanation:
Root = 0, correct guesses = [3,4]
Root = 1, correct guesses = [1,0], [3,4]
Root = 2, correct guesses = [1,0], [2,1], [3,4]
Root = 3, correct guesses = [1,0], [2,1], [3,2], [3,4]
Root = 4, correct guesses = [1,0], [2,1], [3,2]
Considering any node as root will give at least 1 correct guess.
Constraints:
edges.length == n - 12 <= n <= 1051 <= guesses.length <= 1050 <= ai, bi, uj, vj <= n - 1ai != biuj != vjedgesrepresents a valid tree.guesses[j]is an edge of the tree.guessesis unique.0 <= k <= guesses.length
Approaches
2 approaches with complexity analysis and trade-offs.
The most direct way to solve this problem is to test every single node as a potential root. For each node, we can determine the structure of the tree if it were the root, count how many guesses are correct for that structure, and if the count meets the threshold k, we increment our result.
Algorithm
- Build an adjacency list representation of the undirected tree from the
edgesarray.\n- Store theguessesin aHashSetfor quick lookups. Encode each pair(u, v)into a singlelong.\n- Initialize a counter for possible roots,result = 0.\n- Loop through each nodeifrom0ton-1:\n - Considerias the current root.\n - Initializecurrent_correct_guesses = 0.\n - Perform a traversal (BFS or DFS) starting fromi. Keep track of visited nodes.\n - During the traversal, for each edge from a parentuto a childv, check if the guess(u, v)is in theguessSet. If yes, incrementcurrent_correct_guesses.\n - After the traversal is complete, ifcurrent_correct_guesses >= k, incrementresult.\n- Returnresult.
Walkthrough
We can iterate through each node from 0 to n-1. Let's say we are currently checking if node r can be a valid root.\n\nTo find out the parent-child relationships for a tree rooted at r, we can perform a tree traversal like Breadth-First Search (BFS) or Depth-First Search (DFS), starting from r. During the traversal, whenever we move from a node u to an unvisited neighbor v, we establish that u is the parent of v.\n\nTo efficiently check the guesses, we can first store all the guesses in a HashSet of pairs. This allows for O(1) lookups. A pair (u, v) can be encoded into a single long value to be stored in the set.\n\nAs we traverse the tree rooted at r and establish a parent-child relationship u -> v, we check if the pair (u, v) exists in our guessSet. If it does, we increment a counter for correct guesses for the root r.\n\nAfter the traversal for root r is complete, we check if the total count of correct guesses is at least k. If it is, we count r as a possible root.\n\nWe repeat this process for all n nodes and return the total count of possible roots.
import java.util.*; class Solution { public int rootCount(int[][] edges, int[][] guesses, int k) { int n = edges.length + 1; List<List<Integer>> adj = new ArrayList<>(); for (int i = 0; i < n; i++) { adj.add(new ArrayList<>()); } for (int[] edge : edges) { adj.get(edge[0]).add(edge[1]); adj.get(edge[1]).add(edge[0]); } Set<Long> guessSet = new HashSet<>(); long factor = n; for (int[] guess : guesses) { guessSet.add((long) guess[0] * factor + guess[1]); } int possibleRoots = 0; for (int i = 0; i < n; i++) { // Consider node i as the root int correctGuesses = 0; Queue<Integer> q = new LinkedList<>(); boolean[] visited = new boolean[n]; q.add(i); visited[i] = true; while (!q.isEmpty()) { int u = q.poll(); for (int v : adj.get(u)) { if (!visited[v]) { visited[v] = true; q.add(v); // u is parent of v if (guessSet.contains((long) u * factor + v)) { correctGuesses++; } } } } if (correctGuesses >= k) { possibleRoots++; } } return possibleRoots; }}Complexity
Time
O(N^2 + M), where N is the number of nodes and M is the number of guesses. Building the adjacency list takes O(N). Storing guesses in a set takes O(M). The main loop runs N times. Inside the loop, a full tree traversal (BFS/DFS) takes O(N) time. So the loop takes O(N*N). The total complexity is O(N^2 + M). This is too slow for the given constraints.
Space
O(N + M). O(N) for the adjacency list and the visited array. O(M) for the `guessSet`. The queue for BFS would take O(N) in the worst case.
Trade-offs
Pros
Simple to understand and implement.
Cons
Inefficient and will time out on larger test cases due to its quadratic time complexity.
The brute-force approach is inefficient because it recomputes the number of correct guesses from scratch for each potential root. A much more efficient method is the 'rerooting' technique, a form of dynamic programming on trees. We first calculate the answer for an arbitrary root (e.g., node 0), and then in a second pass, we efficiently calculate the answer for every other node by observing how the answer changes when we shift the root to an adjacent node.
Algorithm
- Build an adjacency list for the tree.\n- Store
guessesin aHashSetforO(1)lookups.\n- DFS Pass 1:\n - Start a DFS from an arbitrary root, say node0.\n - For each edgeu -> v(whereuis parent,vis child), check if(u, v)is in theguessSet.\n - Calculate the total number of correct guesses for root0, let's call itcount_root_0.\n- DFS Pass 2:\n - Create an arrayanswerof sizen. Setanswer[0] = count_root_0.\n - Start a second DFS from root0.\n - For a traversal from parentuto childv, calculateanswer[v]using the formula:answer[v] = answer[u] - (is_guess(u, v) ? 1 : 0) + (is_guess(v, u) ? 1 : 0).\n - Recursively call the DFS forvwith its calculated count.\n- Final Count:\n - Iterate through theanswerarray and count how many values are>= k.
Walkthrough
This approach involves two Depth-First Searches (DFS).\n\nFirst Pass (Post-order traversal style): We arbitrarily choose node 0 as the root. We perform a DFS from node 0 to calculate count0, the number of correct guesses if the tree is rooted at 0. During this DFS, when we traverse from a parent u to a child v, we check if the guess (u, v) exists. We sum up these correct guesses over the entire tree.\n\nSecond Pass (Pre-order traversal style): Now, we perform a second DFS, also starting from node 0. This time, we will calculate the number of correct guesses for every node. Let correct_guesses[i] be the number of correct guesses when i is the root. We already know correct_guesses[0] = count0.\n\nWhen we move the root from a parent u to its child v, the orientation of only one edge changes: the edge between u and v. It was u -> v, and now it becomes v -> u. All other parent-child relationships in the tree remain the same relative to their side of this edge.\n\nTherefore, the number of correct guesses for root v can be calculated from the number of correct guesses for root u with a simple adjustment:\ncorrect_guesses[v] = correct_guesses[u] - (is_guess(u, v) ? 1 : 0) + (is_guess(v, u) ? 1 : 0)\nWe use this formula to propagate the counts down the tree in our second DFS.\n\nAfter the second DFS completes, we will have the correct guess count for every possible root stored in an array. We can then iterate through this array and count how many nodes have a score of at least k.
import java.util.*; class Solution { List<List<Integer>> adj; Set<Long> guessSet; int n; int k; int[] correctGuessesCount; int countForRoot0 = 0; long factor; public int rootCount(int[][] edges, int[][] guesses, int k) { this.n = edges.length + 1; this.k = k; this.adj = new ArrayList<>(); for (int i = 0; i < n; i++) { adj.add(new ArrayList<>()); } for (int[] edge : edges) { adj.get(edge[0]).add(edge[1]); adj.get(edge[1]).add(edge[0]); } this.guessSet = new HashSet<>(); this.factor = n; for (int[] guess : guesses) { guessSet.add((long) guess[0] * factor + guess[1]); } // Step 1: DFS from root 0 to calculate initial correct guess count dfs1(0, -1); // Step 2: DFS from root 0 again to calculate counts for all other nodes this.correctGuessesCount = new int[n]; dfs2(0, -1, countForRoot0); // Step 3: Count the number of valid roots int result = 0; for (int count : correctGuessesCount) { if (count >= k) { result++; } } return result; } // DFS to calculate the number of correct guesses for the tree rooted at 0 private void dfs1(int u, int p) { for (int v : adj.get(u)) { if (v == p) continue; // Edge is u -> v if (guessSet.contains(u * factor + v)) { countForRoot0++; } dfs1(v, u); } } // DFS to calculate correct guesses for all nodes using rerooting private void dfs2(int u, int p, int currentCorrectGuesses) { correctGuessesCount[u] = currentCorrectGuesses; for (int v : adj.get(u)) { if (v == p) continue; int nextCorrectGuesses = currentCorrectGuesses; // The edge u -> v is flipped to v -> u // If u -> v was a correct guess, we lose a point if (guessSet.contains(u * factor + v)) { nextCorrectGuesses--; } // If v -> u is a guess, we gain a point if (guessSet.contains(v * factor + u)) { nextCorrectGuesses++; } dfs2(v, u, nextCorrectGuesses); } }}Complexity
Time
O(N + M), where N is the number of nodes and M is the number of guesses. Building the adjacency list is O(N), populating the `guessSet` is O(M). Each of the two DFS passes visits every node and edge once, taking O(N) time. The final counting step takes O(N). Thus, the total time complexity is linear.
Space
O(N + M). O(N) for the adjacency list, O(M) for the `guessSet`, O(N) for the `correctGuessesCount` array, and O(N) for the recursion stack in the worst-case (for a skewed tree).
Trade-offs
Pros
Highly efficient, optimal solution that passes for large constraints.
Cons
More complex to conceptualize and implement compared to the brute-force approach.
Requires understanding of tree traversal and dynamic programming on trees.
Solutions
Solution
class Solution {private List<Integer>[] g;private Map<Long, Integer> gs = new HashMap<>();private int ans;private int k;private int cnt;private int n;public int rootCount(int[][] edges, int[][] guesses, int k) { this.k = k; n = edges.length + 1; g = new List[n]; Arrays.setAll(g, e->new ArrayList<>()); for (var e : edges) { int a = e[0], b = e[1]; g[a].add(b); g[b].add(a); } for (var e : guesses) { int a = e[0], b = e[1]; gs.merge(f(a, b), 1, Integer : : sum); } dfs1(0, -1); dfs2(0, -1); return ans; }private void dfs1(int i, int fa) { for (int j : g[i]) { if (j != fa) { cnt += gs.getOrDefault(f(i, j), 0); dfs1(j, i); } } }private void dfs2(int i, int fa) { ans += cnt >= k ? 1 : 0; for (int j : g[i]) { if (j != fa) { int a = gs.getOrDefault(f(i, j), 0); int b = gs.getOrDefault(f(j, i), 0); cnt -= a; cnt += b; dfs2(j, i); cnt -= b; cnt += a; } } }private long f(int i, int j) { return 1L * i * n + j; }}class Solution {public: int rootCount(vector<vector<int>> &edges, vector<vector<int>> &guesses, int k) { int n = edges.size() + 1; vector<vector<int>> g(n); unordered_map<long long, int> gs; auto f = [&](int i, int j) { return 1LL * i * n + j; }; for (auto &e : edges) { int a = e[0], b = e[1]; g[a].push_back(b); g[b].push_back(a); } for (auto &e : guesses) { int a = e[0], b = e[1]; gs[f(a, b)]++; } int ans = 0; int cnt = 0; function<void(int, int)> dfs1 = [&](int i, int fa) { for (int &j : g[i]) { if (j != fa) { cnt += gs[f(i, j)]; dfs1(j, i); } } }; function<void(int, int)> dfs2 = [&](int i, int fa) { ans += cnt >= k; for (int &j : g[i]) { if (j != fa) { int a = gs[f(i, j)]; int b = gs[f(j, i)]; cnt -= a; cnt += b; dfs2(j, i); cnt -= b; cnt += a; } } }; dfs1(0, -1); dfs2(0, -1); return ans; }};class Solution: def rootCount(self, edges: List[List[int]], guesses: List[List[int]], k: int) -> int: def dfs1(i, fa): nonlocal cnt for j in g[i]: if j != fa: cnt += gs[(i, j)] dfs1(j, i) def dfs2(i, fa): nonlocal ans, cnt ans += cnt >= k for j in g[i]: if j != fa: cnt -= gs[(i, j)] cnt += gs[(j, i)] dfs2(j, i) cnt -= gs[(j, i)] cnt += gs[(i, j)] 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) cnt = 0 dfs1(0, - 1) ans = 0 dfs2(0, - 1) return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.