Find Weighted Median Node in Tree
HardPrompt
You are given an integer n and an undirected, weighted tree rooted at node 0 with n nodes numbered from 0 to n - 1. This is represented by a 2D array edges of length n - 1, where edges[i] = [ui, vi, wi] indicates an edge from node ui to vi with weight wi.
The weighted median node is defined as the first node x on the path from ui to vi such that the sum of edge weights from ui to x is greater than or equal to half of the total path weight.
You are given a 2D integer array queries. For each queries[j] = [uj, vj], determine the weighted median node along the path from uj to vj.
Return an array ans, where ans[j] is the node index of the weighted median for queries[j].
Example 1:
Input: n = 2, edges = [[0,1,7]], queries = [[1,0],[0,1]]
Output: [0,1]
Explanation:

| Query | Path | Edge Weights |
Total Path Weight |
Half | Explanation | Answer |
|---|---|---|---|---|---|---|
[1, 0] |
1 → 0 |
[7] |
7 | 3.5 | Sum from 1 → 0 = 7 >= 3.5, median is node 0. |
0 |
[0, 1] |
0 → 1 |
[7] |
7 | 3.5 | Sum from 0 → 1 = 7 >= 3.5, median is node 1. |
1 |
Example 2:
Input: n = 3, edges = [[0,1,2],[2,0,4]], queries = [[0,1],[2,0],[1,2]]
Output: [1,0,2]
Explanation:

| Query | Path | Edge Weights |
Total Path Weight |
Half | Explanation | Answer |
|---|---|---|---|---|---|---|
[0, 1] |
0 → 1 |
[2] |
2 | 1 | Sum from 0 → 1 = 2 >= 1, median is node 1. |
1 |
[2, 0] |
2 → 0 |
[4] |
4 | 2 | Sum from 2 → 0 = 4 >= 2, median is node 0. |
0 |
[1, 2] |
1 → 0 → 2 |
[2, 4] |
6 | 3 | Sum from 1 → 0 = 2 < 3.Sum from 1 → 2 = 2 + 4 = 6 >= 3, median is node 2. |
2 |
Example 3:
Input: n = 5, edges = [[0,1,2],[0,2,5],[1,3,1],[2,4,3]], queries = [[3,4],[1,2]]
Output: [2,2]
Explanation:

| Query | Path | Edge Weights |
Total Path Weight |
Half | Explanation | Answer |
|---|---|---|---|---|---|---|
[3, 4] |
3 → 1 → 0 → 2 → 4 |
[1, 2, 5, 3] |
11 | 5.5 | Sum from 3 → 1 = 1 < 5.5.Sum from 3 → 0 = 1 + 2 = 3 < 5.5.Sum from 3 → 2 = 1 + 2 + 5 = 8 >= 5.5, median is node 2. |
2 |
[1, 2] |
1 → 0 → 2 |
[2, 5] |
7 | 3.5 |
Sum from |
2 |
Constraints:
2 <= n <= 105edges.length == n - 1edges[i] == [ui, vi, wi]0 <= ui, vi < n1 <= wi <= 1091 <= queries.length <= 105queries[j] == [uj, vj]0 <= uj, vj < n- The input is generated such that
edgesrepresents a valid tree.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach handles each query independently by performing a full graph traversal to find the path between the two specified nodes. Once the path is found, it calculates the total weight and then iterates along the path to locate the weighted median node.
Algorithm
- Build an adjacency list representation of the tree from the
edgesarray. - For each query
(u, v):- Perform a traversal (like BFS) starting from
uto find the path tov. Use a map to store the parent of each visited node to allow path reconstruction. - Once
vis found, trace back fromvtouusing the parent map to get the path. - Iterate through the path to calculate the sum of all edge weights, which is the
total_weight. - Iterate again along the path from
utov, accumulating the edge weights in acurrent_weightvariable. - The first node
x(the destination of an edge) for whichcurrent_weightis at least half oftotal_weightis the median node for the query.
- Perform a traversal (like BFS) starting from
Walkthrough
For every query (u, v), we can find the unique path between them in the tree. A Breadth-First Search (BFS) or Depth-First Search (DFS) starting from u can be used to find v. During the traversal, we maintain a parent map to reconstruct the path once v is reached.
After finding v, we backtrack from v to u using the parent pointers to get the list of nodes on the path. Then, we calculate the total weight of this path by summing up the weights of its constituent edges. Finally, we traverse the path from u to v, keeping a running sum of the edge weights. The first node x where this running sum becomes greater than or equal to half of the total path weight is the answer for the query.
import java.util.*; class Solution { private List<int[]>[] adj; public int[] findWeightedMedian(int n, int[][] edges, int[][] queries) { adj = new ArrayList[n]; for (int i = 0; i < n; i++) { adj[i] = new ArrayList<>(); } for (int[] edge : edges) { adj[edge[0]].add(new int[]{edge[1], edge[2]}); adj[edge[1]].add(new int[]{edge[0], edge[2]}); } int[] ans = new int[queries.length]; for (int i = 0; i < queries.length; i++) { ans[i] = solveQuery(queries[i][0], queries[i][1], n); } return ans; } private int solveQuery(int u, int v, int n) { if (u == v) return u; // 1. Find path from u to v using BFS and parent tracking Map<Integer, Integer> parentMap = new HashMap<>(); Queue<Integer> queue = new LinkedList<>(); queue.add(u); boolean[] visited = new boolean[n]; visited[u] = true; while (!queue.isEmpty()) { int curr = queue.poll(); if (curr == v) break; for (int[] neighbor : adj[curr]) { int nextNode = neighbor[0]; if (!visited[nextNode]) { visited[nextNode] = true; parentMap.put(nextNode, curr); queue.add(nextNode); } } } // 2. Reconstruct path List<Integer> path = new ArrayList<>(); int curr = v; while (parentMap.containsKey(curr)) { path.add(curr); curr = parentMap.get(curr); } path.add(u); Collections.reverse(path); // 3. Calculate total path weight long totalWeight = 0; Map<Integer, Integer> weights = new HashMap<>(); for (int i = 0; i < path.size() - 1; i++) { int node1 = path.get(i); int node2 = path.get(i + 1); int weight = getWeight(node1, node2); totalWeight += weight; weights.put(node2, weight); } // 4. Find median node long currentWeight = 0; for (int i = 1; i < path.size(); i++) { int node = path.get(i); currentWeight += weights.get(node); if (currentWeight * 2 >= totalWeight) { return node; } } return -1; // Should be unreachable for u != v } private int getWeight(int u, int v) { for (int[] neighbor : adj[u]) { if (neighbor[0] == v) { return neighbor[1]; } } return -1; // Should not happen in a connected tree }}Complexity
Time
O(Q * N), where N is the number of nodes and Q is the number of queries. For each query, the traversal (BFS/DFS) to find the path can take up to O(N + E) = O(N) time in the worst case for a tree. Reconstructing and traversing the path also takes up to O(N) time.
Space
O(N) to store the adjacency list, the visited array, the parent map, and the path for each query.
Trade-offs
Pros
Simple to understand and implement.
Does not require complex data structures or algorithms.
Cons
Inefficient for a large number of queries, as it recomputes the path for each query from scratch.
Will result in a 'Time Limit Exceeded' error on platforms with large test cases.
This optimized approach preprocesses the tree to answer path-related queries efficiently. By pre-calculating parent pointers, depths, distances from the root, and a binary lifting table, we can find the Lowest Common Ancestor (LCA) and perform path calculations in logarithmic time per query.
Algorithm
- Preprocessing:
- Represent the tree using an adjacency list.
- Perform a DFS from a root (e.g., node 0) to compute
depth[i],parent[i], anddist[i](weighted distance from root) for each nodei. - Build a binary lifting table
up[i][j]that stores the 2<sup>j</sup>-th ancestor of nodei. This takes O(N log N) time.
- Query Processing (for each query
(u, v)):- Find the LCA,
l = lca(u, v), in O(log N) time using the binary lifting table. - Calculate the total path weight:
total_weight = dist[u] + dist[v] - 2 * dist[l]. - Calculate
half_weight = total_weight / 2.0. - If
dist(u, l) >= half_weight, the median is on theu -> lpath. Use binary lifting to find the nodepjust before the median on this path, and the answer isparent[p]. - Otherwise, the median is on the
l -> vpath. Use binary lifting to find the median node directly by searching for the highest ancestor ofvon the path fromlthat satisfies the median condition.
- Find the LCA,
Walkthrough
The core idea is that the path between any two nodes u and v passes through their Lowest Common Ancestor (LCA). We can decompose the path into u -> ... -> lca(u,v) -> ... -> v.
Preprocessing:
- Root the tree arbitrarily (e.g., at node 0).
- Perform a single DFS traversal from the root to compute:
depth[i]: The depth of each nodei.parent[i]: The immediate parent ofiin the rooted tree.dist[i]: The weighted distance from the root toi.
- Use this information to build a binary lifting (or sparse) table,
up[i][j], which stores the 2<sup>j</sup>-th ancestor of nodei. This allows us to jump up the tree in powers of two.
Query Processing:
For each query (u, v):
- Find their LCA,
l, in O(log N) time using the precomputeduptable. - The total path weight is
dist[u] + dist[v] - 2 * dist[l]. - Calculate
half_weight. - Check if the weighted distance from
utol(dist[u] - dist[l]) is greater than or equal tohalf_weight.- If it is, the median node lies on the
u -> lpath. We can find it by binary lifting up fromu. - Otherwise, the median lies on the
l -> vpath. We can find it by binary lifting up fromv. This binary lifting search for the median node also takes O(log N) time.
- If it is, the median node lies on the
import java.util.*; class Solution { private List<long[]>[] adj; private int n; private int MAX_LOG; private int[] parent; private int[] depth; private long[] dist; private int[][] up; public int[] findWeightedMedian(int n, int[][] edges, int[][] queries) { this.n = n; this.MAX_LOG = (int) Math.ceil(Math.log(n) / Math.log(2)); this.adj = new ArrayList[n]; for (int i = 0; i < n; i++) adj[i] = new ArrayList<>(); for (int[] edge : edges) { adj[edge[0]].add(new long[]{edge[1], edge[2]}); adj[edge[1]].add(new long[]{edge[0], edge[2]}); } this.parent = new int[n]; this.depth = new int[n]; this.dist = new long[n]; this.up = new int[n][MAX_LOG]; dfs(0, -1, 0, 0L); for (int j = 1; j < MAX_LOG; j++) { for (int i = 0; i < n; i++) { if (up[i][j - 1] != -1) { up[i][j] = up[up[i][j - 1]][j - 1]; } else { up[i][j] = -1; } } } int[] ans = new int[queries.length]; for (int i = 0; i < queries.length; i++) { int u = queries[i][0]; int v = queries[i][1]; if (u == v) { ans[i] = u; continue; } int l = lca(u, v); long totalWeight = dist[u] + dist[v] - 2 * dist[l]; double halfWeight = totalWeight / 2.0; long dist_u_l = dist[u] - dist[l]; if (dist_u_l >= halfWeight) { int curr = u; for (int j = MAX_LOG - 1; j >= 0; j--) { if (up[curr][j] != -1 && depth[up[curr][j]] >= depth[l]) { if (dist[u] - dist[up[curr][j]] < halfWeight) { curr = up[curr][j]; } } } ans[i] = parent[curr]; } else { double targetDistFromLca = halfWeight - dist_u_l; int curr = v; for (int j = MAX_LOG - 1; j >= 0; j--) { if (up[curr][j] != -1 && depth[up[curr][j]] >= depth[l]) { if (dist[up[curr][j]] - dist[l] >= targetDistFromLca) { curr = up[curr][j]; } } } ans[i] = curr; } } return ans; } private void dfs(int u, int p, int d, long currentDist) { parent[u] = p; depth[u] = d; dist[u] = currentDist; up[u][0] = p; for (long[] edge : adj[u]) { int v = (int)edge[0]; long w = edge[1]; if (v != p) { dfs(v, u, d + 1, currentDist + w); } } } private int lca(int u, int v) { if (depth[u] < depth[v]) { int temp = u; u = v; v = temp; } for (int j = MAX_LOG - 1; j >= 0; j--) { if (up[u][j] != -1 && depth[u] - (1 << j) >= depth[v]) { u = up[u][j]; } } if (u == v) return u; for (int j = MAX_LOG - 1; j >= 0; j--) { if (up[u][j] != -1 && up[v][j] != -1 && up[u][j] != up[v][j]) { u = up[u][j]; v = up[v][j]; } } return parent[u]; }}Complexity
Time
O(N log N + Q log N). The preprocessing step, dominated by building the binary lifting table, takes O(N log N). Each query takes O(log N) for LCA calculation and another O(log N) for finding the median node.
Space
O(N log N) primarily for the `up` table used for binary lifting. The other arrays (`parent`, `depth`, `dist`) take O(N) space.
Trade-offs
Pros
Highly efficient and scalable for a large number of queries.
The preprocessing step allows for very fast query times.
A standard and powerful technique for solving path-related problems on trees.
Cons
More complex to implement compared to the brute-force approach.
Requires significant space for the binary lifting table.
Video 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.