Number of Ways to Assign Edge Weights II
HardPrompt
There is an undirected tree with n nodes labeled from 1 to n, rooted at node 1. The tree is represented by a 2D integer array edges of length n - 1, where edges[i] = [ui, vi] indicates that there is an edge between nodes ui and vi.
Initially, all edges have a weight of 0. You must assign each edge a weight of either 1 or 2.
The cost of a path between any two nodes u and v is the total weight of all edges in the path connecting them.
You are given a 2D integer array queries. For each queries[i] = [ui, vi], determine the number of ways to assign weights to edges in the path such that the cost of the path between ui and vi is odd.
Return an array answer, where answer[i] is the number of valid assignments for queries[i].
Since the answer may be large, apply modulo 109 + 7 to each answer[i].
Note: For each query, disregard all edges not in the path between node ui and vi.
Example 1:

Input: edges = [[1,2]], queries = [[1,1],[1,2]]
Output: [0,1]
Explanation:
- Query
[1,1]: The path from Node 1 to itself consists of no edges, so the cost is 0. Thus, the number of valid assignments is 0. - Query
[1,2]: The path from Node 1 to Node 2 consists of one edge (1 → 2). Assigning weight 1 makes the cost odd, while 2 makes it even. Thus, the number of valid assignments is 1.
Example 2:

Input: edges = [[1,2],[1,3],[3,4],[3,5]], queries = [[1,4],[3,4],[2,5]]
Output: [2,1,4]
Explanation:
- Query
[1,4]: The path from Node 1 to Node 4 consists of two edges (1 → 3and3 → 4). Assigning weights (1,2) or (2,1) results in an odd cost. Thus, the number of valid assignments is 2. - Query
[3,4]: The path from Node 3 to Node 4 consists of one edge (3 → 4). Assigning weight 1 makes the cost odd, while 2 makes it even. Thus, the number of valid assignments is 1. - Query
[2,5]: The path from Node 2 to Node 5 consists of three edges (2 → 1, 1 → 3, and3 → 5). Assigning (1,2,2), (2,1,2), (2,2,1), or (1,1,1) makes the cost odd. Thus, the number of valid assignments is 4.
Constraints:
2 <= n <= 105edges.length == n - 1edges[i] == [ui, vi]1 <= queries.length <= 105queries[i] == [ui, vi]1 <= ui, vi <= nedgesrepresents a valid tree.
Approaches
2 approaches with complexity analysis and trade-offs.
For each query, this approach finds the path between the two nodes by performing a Breadth-First Search (BFS) starting from one node until the other is found. The length of this path is then used to calculate the number of ways.
Algorithm
- Construct an adjacency list for the tree from the
edgesinput. - Initialize an answer array for the queries.
- For each query
(u, v):- a. If
u == v, the answer is 0. Continue to the next query. - b. Perform a BFS starting from
uto find the shortest path length tov.- i. Create a queue and add
(u, 0). - ii. Create a
visitedset/array and addu. - iii. While the queue is not empty, dequeue
(currentNode, distance). - iv. If
currentNode == v, the path lengthkisdistance. Break the BFS. - v. For each neighbor of
currentNode, if not visited, mark as visited and enqueue(neighbor, distance + 1).
- i. Create a queue and add
- c. Calculate
power(2, k - 1, 10^9 + 7)using modular exponentiation. - d. Store the result in the answer array.
- a. If
- Return the answer array.
Walkthrough
The core idea is to treat each query independently. First, the edges array is used to build an adjacency list representation of the tree. For each query (u, v), a BFS is initiated from node u. A queue stores pairs of (node, distance_from_u), and a visited array prevents redundant exploration. When node v is reached, its corresponding distance from u gives the path length, let's call it k.
The problem asks for the number of ways to assign weights {1, 2} to the k edges in the path such that the total cost is odd. The total cost is odd if and only if an odd number of edges have a weight of 1. For a path of length k, the number of ways to choose an odd number of edges to have weight 1 is given by the sum of binomial coefficients C(k, 1) + C(k, 3) + ..., which is equal to 2^(k-1) for k >= 1. If k = 0 (i.e., u == v), the path has no edges, the cost is 0 (even), so there are 0 ways.
The final answer for a path of length k is 2^(k-1) modulo 10^9 + 7.
Here is the Java implementation:
class Solution { public int[] numberOfWays(int n, int[][] edges, int[][] queries) { 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]); } int[] result = 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) { result[i] = 0; continue; } // BFS to find path length Queue<int[]> queue = new LinkedList<>(); queue.offer(new int[]{u, 0}); // {node, distance} boolean[] visited = new boolean[n + 1]; visited[u] = true; int pathLength = -1; while (!queue.isEmpty()) { int[] current = queue.poll(); int currNode = current[0]; int dist = current[1]; if (currNode == v) { pathLength = dist; break; } for (int neighbor : adj.get(currNode)) { if (!visited[neighbor]) { visited[neighbor] = true; queue.offer(new int[]{neighbor, dist + 1}); } } } result[i] = (int) power(2, pathLength - 1, 1000000007); } return result; } private long power(long base, long exp, long mod) { long res = 1; base %= mod; while (exp > 0) { if (exp % 2 == 1) res = (res * base) % mod; base = (base * base) % mod; exp /= 2; } return res; }}Complexity
Time
`O(Q * N)`, where `Q` is the number of queries and `N` is the number of nodes. For each query, a BFS traversal might visit all `N` nodes and `N-1` edges in the worst case.
Space
`O(N)` for the adjacency list and the data structures used in BFS (queue and visited array).
Trade-offs
Pros
Simple to understand and implement.
Does not require complex data structures or precomputation.
Cons
Highly inefficient for large inputs.
The
O(Q * N)complexity will lead to a 'Time Limit Exceeded' error given the problem constraints.
This approach significantly speeds up the process by precomputing information about the tree. It uses the formula dist(u, v) = depth(u) + depth(v) - 2 * depth(lca(u, v)) to find the path length. The depths of all nodes and a data structure for finding the Lowest Common Ancestor (LCA) efficiently are prepared in a preprocessing step.
Algorithm
- Preprocessing:
- a. Define
MAX_LOG_Nbased onn(e.g.,ceil(log2(n))). - b. Build the adjacency list from
edges. - c. Initialize
depthanduparrays.up[i][j]will store the2^j-th ancestor ofi. - d. Perform a DFS from the root (node 1) to populate
depthandup[i][0](the parent) for all nodes. - e. Fill the rest of the
uptable using the relationup[i][j] = up[up[i][j-1]][j-1]. - f. Precompute powers of 2 modulo
10^9 + 7and store them in an array forO(1)lookup.
- a. Define
- Query Processing:
- a. For each query
(u, v):- i. If
u == v, the answer is 0. - ii. Find
lca_node = lca(u, v)using the binary lifting table. - iii. Calculate path length
k = depth[u] + depth[v] - 2 * depth[lca_node]. - iv. The number of ways is
pow2[k-1]. - v. Store the result.
- i. If
- b. Return the array of results.
- a. For each query
- LCA Function
lca(u, v):-
- Ensure
uis the deeper node by swapping if necessary.
- Ensure
-
- Lift
uup bydepth[u] - depth[v]levels so it's at the same depth asv, using binary jumps.
- Lift
-
- If
u == v, thenuis the LCA. Returnu.
- If
-
- Lift
uandvup together using binary jumps until their parents are the same.
- Lift
-
- The parent,
up[u][0], is the LCA.
- The parent,
-
Walkthrough
The key insight is that the path length k between two nodes u and v in a tree can be calculated quickly if we know their depths and the depth of their LCA. The formula is k = depth(u) + depth[v] - 2 * depth(lca(u, v)). The number of ways to get an odd path cost is then 2^(k-1).
To use this formula, we first preprocess the tree:
- Adjacency List: Build a graph representation.
- DFS Traversal: Perform a single DFS from the root (node 1) to compute the
depthof each node and the immediateparentof each node. - Binary Lifting Table: Use the parent information to build a table,
up[i][j], which stores the2^j-th ancestor of nodei. This is constructed dynamically:up[i][0]is the parent ofi, andup[i][j] = up[up[i][j-1]][j-1]. This table allows us to jump up the tree in powers of two, enabling fast LCA queries.
After this one-time O(N log N) preprocessing, each query (u, v) can be answered efficiently:
- Find their LCA using the binary lifting table in
O(log N)time. - Calculate the path length
kusing the depth formula inO(1). - The result is
2^(k-1)(or 0 ifk=0), which can be found inO(1)if powers of 2 are precomputed.
Here is the Java implementation:
class Solution { private List<List<Integer>> adj; private int[] depth; private int[][] up; private int MAX_LOG_N; private long[] pow2; private final int MOD = 1_000_000_007; public int[] numberOfWays(int n, int[][] edges, int[][] queries) { 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]); } MAX_LOG_N = (int) (Math.log(n) / Math.log(2)) + 1; depth = new int[n + 1]; up = new int[n + 1][MAX_LOG_N]; // Preprocessing dfs(1, 0, 0); for (int j = 1; j < MAX_LOG_N; j++) { for (int i = 1; i <= n; i++) { up[i][j] = up[up[i][j - 1]][j - 1]; } } pow2 = new long[n]; pow2[0] = 1; for (int i = 1; i < n; i++) { pow2[i] = (pow2[i - 1] * 2) % MOD; } // Process queries int[] result = 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) { result[i] = 0; continue; } int lcaNode = lca(u, v); int pathLength = depth[u] + depth[v] - 2 * depth[lcaNode]; result[i] = (int) pow2[pathLength - 1]; } return result; } private void dfs(int u, int p, int d) { depth[u] = d; up[u][0] = p; for (int v : adj.get(u)) { if (v != p) { dfs(v, u, d + 1); } } } private int lca(int u, int v) { if (depth[u] < depth[v]) { int temp = u; u = v; v = temp; } for (int j = MAX_LOG_N - 1; j >= 0; j--) { if (depth[u] - (1 << j) >= depth[v]) { u = up[u][j]; } } if (u == v) { return u; } for (int j = MAX_LOG_N - 1; j >= 0; j--) { if (up[u][j] != up[v][j]) { u = up[u][j]; v = up[v][j]; } } return up[u][0]; }}Complexity
Time
`O(N * log N + Q * log N)`. The preprocessing step takes `O(N * log N)` to build the binary lifting table. Each of the `Q` queries takes `O(log N)` to find the LCA.
Space
`O(N * log N)`. This is dominated by the space required for the `up` table used in binary lifting.
Trade-offs
Pros
Highly efficient for a large number of queries.
It's a standard and powerful technique for tree-based path problems.
Cons
More complex to implement than the brute-force approach.
Requires understanding of LCA and binary lifting.
The space complexity is higher due to 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.