Overflow in intermediate arithmetic
Wrong move: Temporary multiplications exceed integer bounds.
Usually fails on: Large inputs wrap around unexpectedly.
Fix: Use wider types, modular arithmetic, or rearranged operations.
Move from brute-force thinking to an efficient approach using math strategy.
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.
Select any one node x at the maximum depth. Return the number of ways to assign edge weights in the path from node 1 to x such that its total cost is odd.
Since the answer may be large, return it modulo 109 + 7.
Note: Ignore all edges not in the path from node 1 to x.
Example 1:
Input: edges = [[1,2]]
Output: 1
Explanation:
1 → 2).Example 2:
Input: edges = [[1,2],[1,3],[3,4],[3,5]]
Output: 2
Explanation:
1 → 3 and 3 → 4).Constraints:
2 <= n <= 105edges.length == n - 1edges[i] == [ui, vi]1 <= ui, vi <= nedges represents a valid tree.Problem summary: 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. Select any one node x at the maximum depth. Return the number of ways to assign edge weights in the path from node 1 to x such that its total cost is odd. Since the answer may be large, return it modulo 109 + 7. Note: Ignore all edges not in the path from node 1 to x.
Start with the most direct exhaustive search. That gives a correctness anchor before optimizing.
Pattern signal: Math · Tree
[[1,2]]
[[1,2],[1,3],[3,4],[3,5]]
Source-backed implementations are provided below for direct study and interview prep.
// Accepted solution for LeetCode #3558: Number of Ways to Assign Edge Weights I
class Solution {
public int assignEdgeWeights(int[][] edges) {
final int n = edges.size() + 1;
List<Integer>[] graph = new List[n + 1];
Arrays.setAll(graph, i -> new ArrayList<>());
for (int[] edge : edges) {
final int u = edge[0];
final int v = edge[1];
graph[u].add(v);
graph[v].add(u);
}
Queue<Integer> q = new ArrayDeque<>(List.of(1));
boolean[] seen = new boolean[n + 1];
seen[1] = true;
int step = 0;
for (step = 0; !q.isEmpty(); ++step)
for (int sz = q.size(); sz > 0; --sz) {
final int u = q.poll();
for (final int v : graph[u])
if (!seen[v]) {
q.offer(v);
seen[v] = true;
}
}
return step > 0 ? modPow(2, step - 2) : 0;
}
private static final int MOD = 1_000_000_007;
private int modPow(long x, long n) {
if (n == 0)
return 1;
if (n % 2 == 1)
return (int) (x * modPow(x % MOD, (n - 1)) % MOD);
return modPow(x * x % MOD, (n / 2)) % MOD;
}
}
// Accepted solution for LeetCode #3558: Number of Ways to Assign Edge Weights I
package main
// https://space.bilibili.com/206214
func assignEdgeWeights(edges [][]int) int {
n := len(edges) + 1
g := make([][]int, n+1)
for _, e := range edges {
x, y := e[0], e[1]
g[x] = append(g[x], y)
g[y] = append(g[y], x)
}
var dfs func(int, int) int
dfs = func(x, fa int) (d int) {
for _, y := range g[x] {
if y != fa { // 不递归到父节点
d = max(d, dfs(y, x)+1)
}
}
return
}
k := dfs(1, 0)
return pow(2, k-1)
}
func pow(x, n int) int {
const mod = 1_000_000_007
res := 1
for ; n > 0; n /= 2 {
if n%2 > 0 {
res = res * x % mod
}
x = x * x % mod
}
return res
}
# Accepted solution for LeetCode #3558: Number of Ways to Assign Edge Weights I
class Solution:
def assignEdgeWeights(self, edges: list[list[int]]) -> int:
MOD = 1_000_000_007
n = len(edges) + 1
graph = [[] for _ in range(n + 1)]
for u, v in edges:
graph[u].append(v)
graph[v].append(u)
q = collections.deque([1])
seen = {1}
step = 0
while q:
for _ in range(len(q)):
u = q.popleft()
for v in graph[u]:
if v not in seen:
q.append(v)
seen.add(v)
step += 1
return pow(2, step - 2, MOD) if step > 0 else 0
// Accepted solution for LeetCode #3558: Number of Ways to Assign Edge Weights I
// Rust example auto-generated from java reference.
// Replace the signature and local types with the exact LeetCode harness for this problem.
impl Solution {
pub fn rust_example() {
// Port the logic from the reference block below.
}
}
// Reference (java):
// // Accepted solution for LeetCode #3558: Number of Ways to Assign Edge Weights I
// class Solution {
// public int assignEdgeWeights(int[][] edges) {
// final int n = edges.size() + 1;
// List<Integer>[] graph = new List[n + 1];
// Arrays.setAll(graph, i -> new ArrayList<>());
//
// for (int[] edge : edges) {
// final int u = edge[0];
// final int v = edge[1];
// graph[u].add(v);
// graph[v].add(u);
// }
//
// Queue<Integer> q = new ArrayDeque<>(List.of(1));
// boolean[] seen = new boolean[n + 1];
// seen[1] = true;
//
// int step = 0;
// for (step = 0; !q.isEmpty(); ++step)
// for (int sz = q.size(); sz > 0; --sz) {
// final int u = q.poll();
// for (final int v : graph[u])
// if (!seen[v]) {
// q.offer(v);
// seen[v] = true;
// }
// }
//
// return step > 0 ? modPow(2, step - 2) : 0;
// }
//
// private static final int MOD = 1_000_000_007;
//
// private int modPow(long x, long n) {
// if (n == 0)
// return 1;
// if (n % 2 == 1)
// return (int) (x * modPow(x % MOD, (n - 1)) % MOD);
// return modPow(x * x % MOD, (n / 2)) % MOD;
// }
// }
// Accepted solution for LeetCode #3558: Number of Ways to Assign Edge Weights I
// Auto-generated TypeScript example from java.
function exampleSolution(): void {
}
// Reference (java):
// // Accepted solution for LeetCode #3558: Number of Ways to Assign Edge Weights I
// class Solution {
// public int assignEdgeWeights(int[][] edges) {
// final int n = edges.size() + 1;
// List<Integer>[] graph = new List[n + 1];
// Arrays.setAll(graph, i -> new ArrayList<>());
//
// for (int[] edge : edges) {
// final int u = edge[0];
// final int v = edge[1];
// graph[u].add(v);
// graph[v].add(u);
// }
//
// Queue<Integer> q = new ArrayDeque<>(List.of(1));
// boolean[] seen = new boolean[n + 1];
// seen[1] = true;
//
// int step = 0;
// for (step = 0; !q.isEmpty(); ++step)
// for (int sz = q.size(); sz > 0; --sz) {
// final int u = q.poll();
// for (final int v : graph[u])
// if (!seen[v]) {
// q.offer(v);
// seen[v] = true;
// }
// }
//
// return step > 0 ? modPow(2, step - 2) : 0;
// }
//
// private static final int MOD = 1_000_000_007;
//
// private int modPow(long x, long n) {
// if (n == 0)
// return 1;
// if (n % 2 == 1)
// return (int) (x * modPow(x % MOD, (n - 1)) % MOD);
// return modPow(x * x % MOD, (n / 2)) % MOD;
// }
// }
Use this to step through a reusable interview workflow for this problem.
BFS with a queue visits every node exactly once — O(n) time. The queue may hold an entire level of the tree, which for a complete binary tree is up to n/2 nodes = O(n) space. This is optimal in time but costly in space for wide trees.
Every node is visited exactly once, giving O(n) time. Space depends on tree shape: O(h) for recursive DFS (stack depth = height h), or O(w) for BFS (queue width = widest level). For balanced trees h = log n; for skewed trees h = n.
Review these before coding to avoid predictable interview regressions.
Wrong move: Temporary multiplications exceed integer bounds.
Usually fails on: Large inputs wrap around unexpectedly.
Fix: Use wider types, modular arithmetic, or rearranged operations.
Wrong move: Recursive traversal assumes children always exist.
Usually fails on: Leaf nodes throw errors or create wrong depth/path values.
Fix: Handle null/base cases before recursive transitions.